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