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 #pragma once
35 
36 #include "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h"
37 
38 #include "IDatabaseWrapper.h"
39 #include "../DicomInstanceOrigin.h"
40 
41 #include <boost/shared_ptr.hpp>
42 #include <boost/thread/shared_mutex.hpp>
43 
44 
45 namespace Orthanc
46 {
47   class DatabaseLookup;
48   class ParsedDicomFile;
49   struct ServerIndexChange;
50 
51   class StatelessDatabaseOperations : public boost::noncopyable
52   {
53   public:
54     typedef std::list<FileInfo> Attachments;
55     typedef std::map<std::pair<ResourceType, MetadataType>, std::string>  MetadataMap;
56 
57     class ITransactionContext : public IDatabaseListener
58     {
59     public:
~ITransactionContext()60       virtual ~ITransactionContext()
61       {
62       }
63 
64       virtual void Commit() = 0;
65 
66       virtual int64_t GetCompressedSizeDelta() = 0;
67 
68       virtual bool IsUnstableResource(int64_t id) = 0;
69 
70       virtual bool LookupRemainingLevel(std::string& remainingPublicId /* out */,
71                                         ResourceType& remainingLevel   /* out */) = 0;
72 
73       virtual void MarkAsUnstable(int64_t id,
74                                   Orthanc::ResourceType type,
75                                   const std::string& publicId) = 0;
76 
77       virtual void SignalAttachmentsAdded(uint64_t compressedSize) = 0;
78 
79       virtual void SignalChange(const ServerIndexChange& change) = 0;
80     };
81 
82 
83     class ITransactionContextFactory : public boost::noncopyable
84     {
85     public:
~ITransactionContextFactory()86       virtual ~ITransactionContextFactory()
87       {
88       }
89 
90       // WARNING: This method can be invoked from several threads concurrently
91       virtual ITransactionContext* Create() = 0;
92     };
93 
94 
95     class ReadOnlyTransaction : public boost::noncopyable
96     {
97     private:
98       ITransactionContext&  context_;
99 
100     protected:
101       IDatabaseWrapper::ITransaction&  transaction_;
102 
103     public:
ReadOnlyTransaction(IDatabaseWrapper::ITransaction & transaction,ITransactionContext & context)104       explicit ReadOnlyTransaction(IDatabaseWrapper::ITransaction& transaction,
105                                    ITransactionContext& context) :
106         context_(context),
107         transaction_(transaction)
108       {
109       }
110 
GetTransactionContext()111       ITransactionContext& GetTransactionContext()
112       {
113         return context_;
114       }
115 
116       /**
117        * Higher-level constructions
118        **/
119 
120       SeriesStatus GetSeriesStatus(int64_t id,
121                                    int64_t expectedNumberOfInstances);
122 
123 
124       /**
125        * Read-only methods from "IDatabaseWrapper"
126        **/
127 
ApplyLookupResources(std::list<std::string> & resourcesId,std::list<std::string> * instancesId,const std::vector<DatabaseConstraint> & lookup,ResourceType queryLevel,size_t limit)128       void ApplyLookupResources(std::list<std::string>& resourcesId,
129                                 std::list<std::string>* instancesId, // Can be NULL if not needed
130                                 const std::vector<DatabaseConstraint>& lookup,
131                                 ResourceType queryLevel,
132                                 size_t limit)
133       {
134         return transaction_.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel, limit);
135       }
136 
GetAllMetadata(std::map<MetadataType,std::string> & target,int64_t id)137       void GetAllMetadata(std::map<MetadataType, std::string>& target,
138                           int64_t id)
139       {
140         transaction_.GetAllMetadata(target, id);
141       }
142 
GetAllPublicIds(std::list<std::string> & target,ResourceType resourceType)143       void GetAllPublicIds(std::list<std::string>& target,
144                            ResourceType resourceType)
145       {
146         return transaction_.GetAllPublicIds(target, resourceType);
147       }
148 
GetAllPublicIds(std::list<std::string> & target,ResourceType resourceType,size_t since,size_t limit)149       void GetAllPublicIds(std::list<std::string>& target,
150                            ResourceType resourceType,
151                            size_t since,
152                            size_t limit)
153       {
154         return transaction_.GetAllPublicIds(target, resourceType, since, limit);
155       }
156 
GetChanges(std::list<ServerIndexChange> & target,bool & done,int64_t since,uint32_t maxResults)157       void GetChanges(std::list<ServerIndexChange>& target /*out*/,
158                       bool& done /*out*/,
159                       int64_t since,
160                       uint32_t maxResults)
161       {
162         transaction_.GetChanges(target, done, since, maxResults);
163       }
164 
GetChildrenInternalId(std::list<int64_t> & target,int64_t id)165       void GetChildrenInternalId(std::list<int64_t>& target,
166                                  int64_t id)
167       {
168         transaction_.GetChildrenInternalId(target, id);
169       }
170 
GetChildrenPublicId(std::list<std::string> & target,int64_t id)171       void GetChildrenPublicId(std::list<std::string>& target,
172                                int64_t id)
173       {
174         transaction_.GetChildrenPublicId(target, id);
175       }
176 
GetExportedResources(std::list<ExportedResource> & target,bool & done,int64_t since,uint32_t maxResults)177       void GetExportedResources(std::list<ExportedResource>& target /*out*/,
178                                 bool& done /*out*/,
179                                 int64_t since,
180                                 uint32_t maxResults)
181       {
182         return transaction_.GetExportedResources(target, done, since, maxResults);
183       }
184 
GetLastChange(std::list<ServerIndexChange> & target)185       void GetLastChange(std::list<ServerIndexChange>& target /*out*/)
186       {
187         transaction_.GetLastChange(target);
188       }
189 
GetLastExportedResource(std::list<ExportedResource> & target)190       void GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
191       {
192         return transaction_.GetLastExportedResource(target);
193       }
194 
GetLastChangeIndex()195       int64_t GetLastChangeIndex()
196       {
197         return transaction_.GetLastChangeIndex();
198       }
199 
GetMainDicomTags(DicomMap & map,int64_t id)200       void GetMainDicomTags(DicomMap& map,
201                             int64_t id)
202       {
203         transaction_.GetMainDicomTags(map, id);
204       }
205 
GetPublicId(int64_t resourceId)206       std::string GetPublicId(int64_t resourceId)
207       {
208         return transaction_.GetPublicId(resourceId);
209       }
210 
GetResourcesCount(ResourceType resourceType)211       uint64_t GetResourcesCount(ResourceType resourceType)
212       {
213         return transaction_.GetResourcesCount(resourceType);
214       }
215 
GetResourceType(int64_t resourceId)216       ResourceType GetResourceType(int64_t resourceId)
217       {
218         return transaction_.GetResourceType(resourceId);
219       }
220 
GetTotalCompressedSize()221       uint64_t GetTotalCompressedSize()
222       {
223         return transaction_.GetTotalCompressedSize();
224       }
225 
GetTotalUncompressedSize()226       uint64_t GetTotalUncompressedSize()
227       {
228         return transaction_.GetTotalUncompressedSize();
229       }
230 
IsProtectedPatient(int64_t internalId)231       bool IsProtectedPatient(int64_t internalId)
232       {
233         return transaction_.IsProtectedPatient(internalId);
234       }
235 
ListAvailableAttachments(std::set<FileContentType> & target,int64_t id)236       void ListAvailableAttachments(std::set<FileContentType>& target,
237                                     int64_t id)
238       {
239         transaction_.ListAvailableAttachments(target, id);
240       }
241 
LookupAttachment(FileInfo & attachment,int64_t & revision,int64_t id,FileContentType contentType)242       bool LookupAttachment(FileInfo& attachment,
243                             int64_t& revision,
244                             int64_t id,
245                             FileContentType contentType)
246       {
247         return transaction_.LookupAttachment(attachment, revision, id, contentType);
248       }
249 
LookupGlobalProperty(std::string & target,GlobalProperty property,bool shared)250       bool LookupGlobalProperty(std::string& target,
251                                 GlobalProperty property,
252                                 bool shared)
253       {
254         return transaction_.LookupGlobalProperty(target, property, shared);
255       }
256 
LookupMetadata(std::string & target,int64_t & revision,int64_t id,MetadataType type)257       bool LookupMetadata(std::string& target,
258                           int64_t& revision,
259                           int64_t id,
260                           MetadataType type)
261       {
262         return transaction_.LookupMetadata(target, revision, id, type);
263       }
264 
LookupParent(int64_t & parentId,int64_t resourceId)265       bool LookupParent(int64_t& parentId,
266                         int64_t resourceId)
267       {
268         return transaction_.LookupParent(parentId, resourceId);
269       }
270 
LookupResource(int64_t & id,ResourceType & type,const std::string & publicId)271       bool LookupResource(int64_t& id,
272                           ResourceType& type,
273                           const std::string& publicId)
274       {
275         return transaction_.LookupResource(id, type, publicId);
276       }
277 
LookupResourceAndParent(int64_t & id,ResourceType & type,std::string & parentPublicId,const std::string & publicId)278       bool LookupResourceAndParent(int64_t& id,
279                                    ResourceType& type,
280                                    std::string& parentPublicId,
281                                    const std::string& publicId)
282       {
283         return transaction_.LookupResourceAndParent(id, type, parentPublicId, publicId);
284       }
285     };
286 
287 
288     class ReadWriteTransaction : public ReadOnlyTransaction
289     {
290     public:
ReadWriteTransaction(IDatabaseWrapper::ITransaction & transaction,ITransactionContext & context)291       ReadWriteTransaction(IDatabaseWrapper::ITransaction& transaction,
292                            ITransactionContext& context) :
293         ReadOnlyTransaction(transaction, context)
294       {
295       }
296 
AddAttachment(int64_t id,const FileInfo & attachment,int64_t revision)297       void AddAttachment(int64_t id,
298                          const FileInfo& attachment,
299                          int64_t revision)
300       {
301         transaction_.AddAttachment(id, attachment, revision);
302       }
303 
ClearChanges()304       void ClearChanges()
305       {
306         transaction_.ClearChanges();
307       }
308 
ClearExportedResources()309       void ClearExportedResources()
310       {
311         transaction_.ClearExportedResources();
312       }
313 
ClearMainDicomTags(int64_t id)314       void ClearMainDicomTags(int64_t id)
315       {
316         return transaction_.ClearMainDicomTags(id);
317       }
318 
CreateInstance(IDatabaseWrapper::CreateInstanceResult & result,int64_t & instanceId,const std::string & patient,const std::string & study,const std::string & series,const std::string & instance)319       bool CreateInstance(IDatabaseWrapper::CreateInstanceResult& result, /* out */
320                           int64_t& instanceId,          /* out */
321                           const std::string& patient,
322                           const std::string& study,
323                           const std::string& series,
324                           const std::string& instance)
325       {
326         return transaction_.CreateInstance(result, instanceId, patient, study, series, instance);
327       }
328 
DeleteAttachment(int64_t id,FileContentType attachment)329       void DeleteAttachment(int64_t id,
330                             FileContentType attachment)
331       {
332         return transaction_.DeleteAttachment(id, attachment);
333       }
334 
DeleteMetadata(int64_t id,MetadataType type)335       void DeleteMetadata(int64_t id,
336                           MetadataType type)
337       {
338         transaction_.DeleteMetadata(id, type);
339       }
340 
DeleteResource(int64_t id)341       void DeleteResource(int64_t id)
342       {
343         transaction_.DeleteResource(id);
344       }
345 
346       void LogChange(int64_t internalId,
347                      ChangeType changeType,
348                      ResourceType resourceType,
349                      const std::string& publicId);
350 
LogExportedResource(const ExportedResource & resource)351       void LogExportedResource(const ExportedResource& resource)
352       {
353         transaction_.LogExportedResource(resource);
354       }
355 
SetGlobalProperty(GlobalProperty property,bool shared,const std::string & value)356       void SetGlobalProperty(GlobalProperty property,
357                              bool shared,
358                              const std::string& value)
359       {
360         transaction_.SetGlobalProperty(property, shared, value);
361       }
362 
SetMetadata(int64_t id,MetadataType type,const std::string & value,int64_t revision)363       void SetMetadata(int64_t id,
364                        MetadataType type,
365                        const std::string& value,
366                        int64_t revision)
367       {
368         return transaction_.SetMetadata(id, type, value, revision);
369       }
370 
SetProtectedPatient(int64_t internalId,bool isProtected)371       void SetProtectedPatient(int64_t internalId,
372                                bool isProtected)
373       {
374         transaction_.SetProtectedPatient(internalId, isProtected);
375       }
376 
SetResourcesContent(const ResourcesContent & content)377       void SetResourcesContent(const ResourcesContent& content)
378       {
379         transaction_.SetResourcesContent(content);
380       }
381 
382       void Recycle(uint64_t maximumStorageSize,
383                    unsigned int maximumPatients,
384                    uint64_t addedInstanceSize,
385                    const std::string& newPatientId);
386     };
387 
388 
389     class IReadOnlyOperations : public boost::noncopyable
390     {
391     public:
~IReadOnlyOperations()392       virtual ~IReadOnlyOperations()
393       {
394       }
395 
396       virtual void Apply(ReadOnlyTransaction& transaction) = 0;
397     };
398 
399 
400     class IReadWriteOperations : public boost::noncopyable
401     {
402     public:
~IReadWriteOperations()403       virtual ~IReadWriteOperations()
404       {
405       }
406 
407       virtual void Apply(ReadWriteTransaction& transaction) = 0;
408     };
409 
410 
411   private:
412     class MainDicomTagsRegistry;
413     class Transaction;
414 
415     IDatabaseWrapper&                            db_;
416     boost::shared_ptr<MainDicomTagsRegistry>     mainDicomTagsRegistry_;  // "shared_ptr" because of PImpl
417     bool                                         hasFlushToDisk_;
418 
419     // Mutex to protect the configuration options
420     boost::shared_mutex                          mutex_;
421     std::unique_ptr<ITransactionContextFactory>  factory_;
422     unsigned int                                 maxRetries_;
423 
424     void NormalizeLookup(std::vector<DatabaseConstraint>& target,
425                          const DatabaseLookup& source,
426                          ResourceType level) const;
427 
428     void ApplyInternal(IReadOnlyOperations* readOperations,
429                        IReadWriteOperations* writeOperations);
430 
431   protected:
432     void StandaloneRecycling(uint64_t maximumStorageSize,
433                              unsigned int maximumPatientCount);
434 
435   public:
436     explicit StatelessDatabaseOperations(IDatabaseWrapper& database);
437 
438     void SetTransactionContextFactory(ITransactionContextFactory* factory /* takes ownership */);
439 
440     // Only used to handle "ErrorCode_DatabaseCannotSerialize" in the
441     // case of collision between multiple writers
442     void SetMaxDatabaseRetries(unsigned int maxRetries);
443 
444     // It is assumed that "GetDatabaseVersion()" can run out of a
445     // database transaction
GetDatabaseVersion()446     unsigned int GetDatabaseVersion()
447     {
448       return db_.GetDatabaseVersion();
449     }
450 
451     void FlushToDisk();
452 
HasFlushToDisk()453     bool HasFlushToDisk() const
454     {
455       return hasFlushToDisk_;
456     }
457 
458     void Apply(IReadOnlyOperations& operations);
459 
460     void Apply(IReadWriteOperations& operations);
461 
462     bool ExpandResource(Json::Value& target,
463                         const std::string& publicId,
464                         ResourceType level,
465                         DicomToJsonFormat format);
466 
467     void GetAllMetadata(std::map<MetadataType, std::string>& target,
468                         const std::string& publicId,
469                         ResourceType level);
470 
471     void GetAllUuids(std::list<std::string>& target,
472                      ResourceType resourceType);
473 
474     void GetAllUuids(std::list<std::string>& target,
475                      ResourceType resourceType,
476                      size_t since,
477                      size_t limit);
478 
479     void GetGlobalStatistics(/* out */ uint64_t& diskSize,
480                              /* out */ uint64_t& uncompressedSize,
481                              /* out */ uint64_t& countPatients,
482                              /* out */ uint64_t& countStudies,
483                              /* out */ uint64_t& countSeries,
484                              /* out */ uint64_t& countInstances);
485 
486     bool LookupAttachment(FileInfo& attachment,
487                           int64_t& revision,
488                           const std::string& instancePublicId,
489                           FileContentType contentType);
490 
491     void GetChanges(Json::Value& target,
492                     int64_t since,
493                     unsigned int maxResults);
494 
495     void GetLastChange(Json::Value& target);
496 
497     void GetExportedResources(Json::Value& target,
498                               int64_t since,
499                               unsigned int maxResults);
500 
501     void GetLastExportedResource(Json::Value& target);
502 
503     bool IsProtectedPatient(const std::string& publicId);
504 
505     void GetChildren(std::list<std::string>& result,
506                      const std::string& publicId);
507 
508     void GetChildInstances(std::list<std::string>& result,
509                            const std::string& publicId);
510 
511     bool LookupMetadata(std::string& target,
512                         int64_t& revision,
513                         const std::string& publicId,
514                         ResourceType expectedType,
515                         MetadataType type);
516 
517     void ListAvailableAttachments(std::set<FileContentType>& target,
518                                   const std::string& publicId,
519                                   ResourceType expectedType);
520 
521     bool LookupParent(std::string& target,
522                       const std::string& publicId);
523 
524     void GetResourceStatistics(/* out */ ResourceType& type,
525                                /* out */ uint64_t& diskSize,
526                                /* out */ uint64_t& uncompressedSize,
527                                /* out */ unsigned int& countStudies,
528                                /* out */ unsigned int& countSeries,
529                                /* out */ unsigned int& countInstances,
530                                /* out */ uint64_t& dicomDiskSize,
531                                /* out */ uint64_t& dicomUncompressedSize,
532                                const std::string& publicId);
533 
534     void LookupIdentifierExact(std::vector<std::string>& result,
535                                ResourceType level,
536                                const DicomTag& tag,
537                                const std::string& value);
538 
539     bool LookupGlobalProperty(std::string& value,
540                               GlobalProperty property,
541                               bool shared);
542 
543     std::string GetGlobalProperty(GlobalProperty property,
544                                   bool shared,
545                                   const std::string& defaultValue);
546 
547     bool GetMainDicomTags(DicomMap& result,
548                           const std::string& publicId,
549                           ResourceType expectedType,
550                           ResourceType levelOfInterest);
551 
552     // Only applicable at the instance level
553     bool GetAllMainDicomTags(DicomMap& result,
554                              const std::string& instancePublicId);
555 
556     bool LookupResourceType(ResourceType& type,
557                             const std::string& publicId);
558 
559     bool LookupParent(std::string& target,
560                       const std::string& publicId,
561                       ResourceType parentType);
562 
563     void ApplyLookupResources(std::vector<std::string>& resourcesId,
564                               std::vector<std::string>* instancesId,  // Can be NULL if not needed
565                               const DatabaseLookup& lookup,
566                               ResourceType queryLevel,
567                               size_t limit);
568 
569     bool DeleteResource(Json::Value& remainingAncestor /* out */,
570                         const std::string& uuid,
571                         ResourceType expectedType);
572 
573     void LogExportedResource(const std::string& publicId,
574                              const std::string& remoteModality);
575 
576     void SetProtectedPatient(const std::string& publicId,
577                              bool isProtected);
578 
579     void SetMetadata(int64_t& newRevision /*out*/,
580                      const std::string& publicId,
581                      MetadataType type,
582                      const std::string& value,
583                      bool hasOldRevision,
584                      int64_t oldRevision,
585                      const std::string& oldMD5);
586 
587     // Same as "SetMetadata()", but doesn't care about revisions
588     void OverwriteMetadata(const std::string& publicId,
589                            MetadataType type,
590                            const std::string& value);
591 
592     bool DeleteMetadata(const std::string& publicId,
593                         MetadataType type,
594                         bool hasRevision,
595                         int64_t revision,
596                         const std::string& md5);
597 
598     uint64_t IncrementGlobalSequence(GlobalProperty sequence,
599                                      bool shared);
600 
601     void DeleteChanges();
602 
603     void DeleteExportedResources();
604 
605     void SetGlobalProperty(GlobalProperty property,
606                            bool shared,
607                            const std::string& value);
608 
609     bool DeleteAttachment(const std::string& publicId,
610                           FileContentType type,
611                           bool hasRevision,
612                           int64_t revision,
613                           const std::string& md5);
614 
615     void LogChange(int64_t internalId,
616                    ChangeType changeType,
617                    const std::string& publicId,
618                    ResourceType level);
619 
620     void ReconstructInstance(const ParsedDicomFile& dicom);
621 
622     StoreStatus Store(std::map<MetadataType, std::string>& instanceMetadata,
623                       const DicomMap& dicomSummary,
624                       const Attachments& attachments,
625                       const MetadataMap& metadata,
626                       const DicomInstanceOrigin& origin,
627                       bool overwrite,
628                       bool hasTransferSyntax,
629                       DicomTransferSyntax transferSyntax,
630                       bool hasPixelDataOffset,
631                       uint64_t pixelDataOffset,
632                       uint64_t maximumStorageSize,
633                       unsigned int maximumPatients);
634 
635     StoreStatus AddAttachment(int64_t& newRevision /*out*/,
636                               const FileInfo& attachment,
637                               const std::string& publicId,
638                               uint64_t maximumStorageSize,
639                               unsigned int maximumPatients,
640                               bool hasOldRevision,
641                               int64_t oldRevision,
642                               const std::string& oldMd5);
643   };
644 }
645