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 Affero General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  **/
20 
21 
22 #include "StorageBackend.h"
23 
24 #if HAS_ORTHANC_EXCEPTION != 1
25 #  error HAS_ORTHANC_EXCEPTION must be set to 1
26 #endif
27 
28 #include "../../Framework/Common/BinaryStringValue.h"
29 #include "../../Framework/Common/ResultFileValue.h"
30 
31 #include <Compatibility.h>  // For std::unique_ptr<>
32 #include <Logging.h>
33 #include <OrthancException.h>
34 
35 #include <boost/thread.hpp>
36 #include <cassert>
37 #include <limits>
38 
39 
40 #define ORTHANC_PLUGINS_DATABASE_CATCH                                  \
41   catch (::Orthanc::OrthancException& e)                                \
42   {                                                                     \
43     return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());       \
44   }                                                                     \
45   catch (::std::runtime_error& e)                                       \
46   {                                                                     \
47     std::string s = "Exception in storage area back-end: " + std::string(e.what()); \
48     OrthancPluginLogError(context_, s.c_str());                         \
49     return OrthancPluginErrorCode_DatabasePlugin;                       \
50   }                                                                     \
51   catch (...)                                                           \
52   {                                                                     \
53     OrthancPluginLogError(context_, "Native exception");                \
54     return OrthancPluginErrorCode_DatabasePlugin;                       \
55   }
56 
57 
58 namespace OrthancDatabases
59 {
60   class StorageBackend::ReadWholeOperation : public StorageBackend::IDatabaseOperation
61   {
62   private:
63     IFileContentVisitor&      visitor_;
64     const char*               uuid_;
65     OrthancPluginContentType  type_;
66 
67   public:
ReadWholeOperation(IFileContentVisitor & visitor,const char * uuid,OrthancPluginContentType type)68     ReadWholeOperation(IFileContentVisitor& visitor,
69                        const char* uuid,
70                        OrthancPluginContentType type) :
71       visitor_(visitor),
72       uuid_(uuid),
73       type_(type)
74     {
75     }
76 
Execute(StorageBackend::IAccessor & accessor)77     virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE
78     {
79       accessor.ReadWhole(visitor_, uuid_, type_);
80     }
81   };
82 
83 
StorageBackend(IDatabaseFactory * factory,unsigned int maxRetries)84   StorageBackend::StorageBackend(IDatabaseFactory* factory,
85                                  unsigned int maxRetries) :
86     manager_(factory),
87     maxRetries_(maxRetries)
88   {
89   }
90 
Create(const std::string & uuid,const void * content,size_t size,OrthancPluginContentType type)91   void StorageBackend::AccessorBase::Create(const std::string& uuid,
92                                             const void* content,
93                                             size_t size,
94                                             OrthancPluginContentType type)
95   {
96     DatabaseManager::Transaction transaction(manager_, TransactionType_ReadWrite);
97 
98     {
99       DatabaseManager::CachedStatement statement(
100         STATEMENT_FROM_HERE, manager_,
101         "INSERT INTO StorageArea VALUES (${uuid}, ${content}, ${type})");
102 
103       statement.SetParameterType("uuid", ValueType_Utf8String);
104       statement.SetParameterType("content", ValueType_InputFile);
105       statement.SetParameterType("type", ValueType_Integer64);
106 
107       Dictionary args;
108       args.SetUtf8Value("uuid", uuid);
109       args.SetFileValue("content", content, size);
110       args.SetIntegerValue("type", type);
111 
112       statement.Execute(args);
113     }
114 
115     transaction.Commit();
116   }
117 
118 
ReadWhole(StorageBackend::IFileContentVisitor & visitor,const std::string & uuid,OrthancPluginContentType type)119   void StorageBackend::AccessorBase::ReadWhole(StorageBackend::IFileContentVisitor& visitor,
120                                                const std::string& uuid,
121                                                OrthancPluginContentType type)
122   {
123     DatabaseManager::Transaction transaction(manager_, TransactionType_ReadOnly);
124 
125     {
126       DatabaseManager::CachedStatement statement(
127         STATEMENT_FROM_HERE, manager_,
128         "SELECT content FROM StorageArea WHERE uuid=${uuid} AND type=${type}");
129 
130       statement.SetParameterType("uuid", ValueType_Utf8String);
131       statement.SetParameterType("type", ValueType_Integer64);
132 
133       Dictionary args;
134       args.SetUtf8Value("uuid", uuid);
135       args.SetIntegerValue("type", type);
136 
137       statement.Execute(args);
138 
139       if (statement.IsDone())
140       {
141         throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
142       }
143       else if (statement.GetResultFieldsCount() != 1)
144       {
145         throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
146       }
147       else
148       {
149         const IValue& value = statement.GetResultField(0);
150 
151         switch (value.GetType())
152         {
153           case ValueType_ResultFile:
154           {
155             std::string content;
156             dynamic_cast<const ResultFileValue&>(value).ReadWhole(content);
157             visitor.Assign(content);
158             break;
159           }
160 
161           case ValueType_BinaryString:
162             visitor.Assign(dynamic_cast<const BinaryStringValue&>(value).GetContent());
163             break;
164 
165           default:
166             throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
167         }
168       }
169     }
170 
171     transaction.Commit();
172 
173     if (!visitor.IsSuccess())
174     {
175       throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Could not read attachment from the storage area");
176     }
177   }
178 
179 
ReadRange(IFileContentVisitor & visitor,const std::string & uuid,OrthancPluginContentType type,uint64_t start,size_t length)180   void StorageBackend::AccessorBase::ReadRange(IFileContentVisitor& visitor,
181                                                const std::string& uuid,
182                                                OrthancPluginContentType type,
183                                                uint64_t start,
184                                                size_t length)
185   {
186     /**
187      * This is a generic implementation, that will only work if
188      * "ResultFileValue" is implemented by the database backend. For
189      * instance, this will *not* work with MySQL, as the latter uses
190      * BLOB columns to store files.
191      **/
192     DatabaseManager::Transaction transaction(manager_, TransactionType_ReadOnly);
193 
194     {
195       DatabaseManager::CachedStatement statement(
196         STATEMENT_FROM_HERE, manager_,
197         "SELECT content FROM StorageArea WHERE uuid=${uuid} AND type=${type}");
198 
199       statement.SetParameterType("uuid", ValueType_Utf8String);
200       statement.SetParameterType("type", ValueType_Integer64);
201 
202       Dictionary args;
203       args.SetUtf8Value("uuid", uuid);
204       args.SetIntegerValue("type", type);
205 
206       statement.Execute(args);
207 
208       if (statement.IsDone())
209       {
210         throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
211       }
212       else if (statement.GetResultFieldsCount() != 1)
213       {
214         throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
215       }
216       else
217       {
218         const IValue& value = statement.GetResultField(0);
219         if (value.GetType() == ValueType_ResultFile)
220         {
221           std::string content;
222           dynamic_cast<const ResultFileValue&>(value).ReadRange(content, start, length);
223           visitor.Assign(content);
224         }
225         else
226         {
227           throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
228         }
229       }
230     }
231 
232     transaction.Commit();
233 
234     if (!visitor.IsSuccess())
235     {
236       throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Could not read attachment from the storage area");
237     }
238   }
239 
240 
Remove(const std::string & uuid,OrthancPluginContentType type)241   void StorageBackend::AccessorBase::Remove(const std::string& uuid,
242                                             OrthancPluginContentType type)
243   {
244     DatabaseManager::Transaction transaction(manager_, TransactionType_ReadWrite);
245 
246     {
247       DatabaseManager::CachedStatement statement(
248         STATEMENT_FROM_HERE, manager_,
249         "DELETE FROM StorageArea WHERE uuid=${uuid} AND type=${type}");
250 
251       statement.SetParameterType("uuid", ValueType_Utf8String);
252       statement.SetParameterType("type", ValueType_Integer64);
253 
254       Dictionary args;
255       args.SetUtf8Value("uuid", uuid);
256       args.SetIntegerValue("type", type);
257 
258       statement.Execute(args);
259     }
260 
261     transaction.Commit();
262   }
263 
264 
265   static OrthancPluginContext* context_ = NULL;
266   static std::unique_ptr<StorageBackend>  backend_;
267 
268 
StorageCreate(const char * uuid,const void * content,int64_t size,OrthancPluginContentType type)269   static OrthancPluginErrorCode StorageCreate(const char* uuid,
270                                               const void* content,
271                                               int64_t size,
272                                               OrthancPluginContentType type)
273   {
274     class Operation : public StorageBackend::IDatabaseOperation
275     {
276     private:
277       const char*               uuid_;
278       const void*               content_;
279       int64_t                   size_;
280       OrthancPluginContentType  type_;
281 
282     public:
283       Operation(const char* uuid,
284                 const void* content,
285                 int64_t size,
286                 OrthancPluginContentType type) :
287         uuid_(uuid),
288         content_(content),
289         size_(size),
290         type_(type)
291       {
292       }
293 
294       virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE
295       {
296         accessor.Create(uuid_, content_, size_, type_);
297       }
298     };
299 
300 
301     try
302     {
303       if (backend_.get() == NULL)
304       {
305         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
306       }
307       else
308       {
309         Operation operation(uuid, content, size, type);
310         backend_->Execute(operation);
311         return OrthancPluginErrorCode_Success;
312       }
313     }
314     ORTHANC_PLUGINS_DATABASE_CATCH;
315   }
316 
317 
318 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
319 #  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 0)
StorageReadWhole(OrthancPluginMemoryBuffer64 * target,const char * uuid,OrthancPluginContentType type)320   static OrthancPluginErrorCode StorageReadWhole(OrthancPluginMemoryBuffer64* target,
321                                                  const char* uuid,
322                                                  OrthancPluginContentType type)
323   {
324     class Visitor : public StorageBackend::IFileContentVisitor
325     {
326     private:
327       OrthancPluginMemoryBuffer64* target_;
328       bool                         success_;
329 
330     public:
331       Visitor(OrthancPluginMemoryBuffer64* target) :
332         target_(target),
333         success_(false)
334       {
335         if (target == NULL)
336         {
337           throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
338         }
339       }
340 
341       virtual bool IsSuccess() const ORTHANC_OVERRIDE
342       {
343         return success_;
344       }
345 
346       virtual void Assign(const std::string& content) ORTHANC_OVERRIDE
347       {
348         if (success_)
349         {
350           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
351         }
352         else
353         {
354           assert(context_ != NULL);
355 
356           if (OrthancPluginCreateMemoryBuffer64(context_, target_, static_cast<uint64_t>(content.size())) !=
357               OrthancPluginErrorCode_Success)
358           {
359             throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
360           }
361 
362           if (!content.empty())
363           {
364             memcpy(target_->data, content.c_str(), content.size());
365           }
366 
367           success_ = true;
368         }
369       }
370     };
371 
372 
373     try
374     {
375       if (backend_.get() == NULL)
376       {
377         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
378       }
379       else
380       {
381         Visitor visitor(target);
382 
383         {
384           StorageBackend::ReadWholeOperation operation(visitor, uuid, type);
385           backend_->Execute(operation);
386         }
387 
388         return OrthancPluginErrorCode_Success;
389       }
390     }
391     ORTHANC_PLUGINS_DATABASE_CATCH;
392   }
393 
394 
StorageReadRange(OrthancPluginMemoryBuffer64 * target,const char * uuid,OrthancPluginContentType type,uint64_t start)395   static OrthancPluginErrorCode StorageReadRange(OrthancPluginMemoryBuffer64* target,
396                                                  const char* uuid,
397                                                  OrthancPluginContentType type,
398                                                  uint64_t start)
399   {
400     class Visitor : public StorageBackend::IFileContentVisitor
401     {
402     private:
403       OrthancPluginMemoryBuffer64* target_;  // This buffer is already allocated by the Orthanc core
404       bool                         success_;
405 
406     public:
407       Visitor(OrthancPluginMemoryBuffer64* target) :
408         target_(target),
409         success_(false)
410       {
411         if (target == NULL)
412         {
413           throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
414         }
415       }
416 
417       virtual bool IsSuccess() const ORTHANC_OVERRIDE
418       {
419         return success_;
420       }
421 
422       virtual void Assign(const std::string& content) ORTHANC_OVERRIDE
423       {
424         if (success_)
425         {
426           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
427         }
428         else
429         {
430           if (content.size() != target_->size)
431           {
432             throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
433           }
434 
435           if (!content.empty())
436           {
437             memcpy(target_->data, content.c_str(), content.size());
438           }
439 
440           success_ = true;
441         }
442       }
443     };
444 
445 
446     class Operation : public StorageBackend::IDatabaseOperation
447     {
448     private:
449       Visitor&                  visitor_;
450       const char*               uuid_;
451       OrthancPluginContentType  type_;
452       uint64_t                  start_;
453       size_t                    length_;
454 
455     public:
456       Operation(Visitor& visitor,
457                 const char* uuid,
458                 OrthancPluginContentType type,
459                 uint64_t start,
460                 size_t length) :
461         visitor_(visitor),
462         uuid_(uuid),
463         type_(type),
464         start_(start),
465         length_(length)
466       {
467       }
468 
469       virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE
470       {
471         accessor.ReadRange(visitor_, uuid_, type_, start_, length_);
472       }
473     };
474 
475 
476     try
477     {
478       if (backend_.get() == NULL)
479       {
480         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
481       }
482       else
483       {
484         Visitor visitor(target);
485 
486         {
487           Operation operation(visitor, uuid, type, start, target->size);
488           backend_->Execute(operation);
489         }
490 
491         return OrthancPluginErrorCode_Success;
492       }
493     }
494     ORTHANC_PLUGINS_DATABASE_CATCH;
495   }
496 #  endif
497 #endif
498 
499 
StorageRead(void ** data,int64_t * size,const char * uuid,OrthancPluginContentType type)500   static OrthancPluginErrorCode StorageRead(void** data,
501                                             int64_t* size,
502                                             const char* uuid,
503                                             OrthancPluginContentType type)
504   {
505     class Visitor : public StorageBackend::IFileContentVisitor
506     {
507     private:
508       void**    data_;
509       int64_t*  size_;
510       bool      success_;
511 
512     public:
513       Visitor(void** data,
514               int64_t* size) :
515         data_(data),
516         size_(size),
517         success_(false)
518       {
519       }
520 
521       ~Visitor()
522       {
523         if (data_ != NULL /* this condition is invalidated by "Release()" */ &&
524             *data_ != NULL)
525         {
526           free(*data_);
527         }
528       }
529 
530       void Release()
531       {
532         data_ = NULL;
533       }
534 
535       virtual bool IsSuccess() const ORTHANC_OVERRIDE
536       {
537         return success_;
538       }
539 
540       virtual void Assign(const std::string& content) ORTHANC_OVERRIDE
541       {
542         if (success_)
543         {
544           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
545         }
546         else if (data_ == NULL)
547         {
548           // "Release()" has been called
549           throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
550         }
551         else
552         {
553           if (content.empty())
554           {
555             *data_ = NULL;
556             *size_ = 0;
557           }
558           else
559           {
560             *size_ = static_cast<int64_t>(content.size());
561 
562             if (static_cast<size_t>(*size_) != content.size())
563             {
564               throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory,
565                                               "File cannot be stored in a 63bit buffer");
566             }
567 
568             *data_ = malloc(*size_);
569             if (*data_ == NULL)
570             {
571               throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
572             }
573 
574             memcpy(*data_, content.c_str(), *size_);
575           }
576 
577           success_ = true;
578         }
579       }
580     };
581 
582 
583     try
584     {
585       if (backend_.get() == NULL)
586       {
587         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
588       }
589       else if (data == NULL ||
590                size == NULL)
591       {
592         throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
593       }
594       else
595       {
596         Visitor visitor(data, size);
597 
598         {
599           StorageBackend::ReadWholeOperation operation(visitor, uuid, type);
600           backend_->Execute(operation);
601         }
602 
603         visitor.Release();
604 
605         return OrthancPluginErrorCode_Success;
606       }
607     }
608     ORTHANC_PLUGINS_DATABASE_CATCH;
609   }
610 
611 
StorageRemove(const char * uuid,OrthancPluginContentType type)612   static OrthancPluginErrorCode StorageRemove(const char* uuid,
613                                               OrthancPluginContentType type)
614   {
615     class Operation : public StorageBackend::IDatabaseOperation
616     {
617     private:
618       const char*               uuid_;
619       OrthancPluginContentType  type_;
620 
621     public:
622       Operation(const char* uuid,
623                 OrthancPluginContentType type) :
624         uuid_(uuid),
625         type_(type)
626       {
627       }
628 
629       virtual void Execute(StorageBackend::IAccessor& accessor) ORTHANC_OVERRIDE
630       {
631         accessor.Remove(uuid_, type_);
632       }
633     };
634 
635 
636     try
637     {
638       if (backend_.get() == NULL)
639       {
640         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
641       }
642       else
643       {
644         Operation operation(uuid, type);
645         backend_->Execute(operation);
646         return OrthancPluginErrorCode_Success;
647       }
648     }
649     ORTHANC_PLUGINS_DATABASE_CATCH;
650   }
651 
652 
Register(OrthancPluginContext * context,StorageBackend * backend)653   void StorageBackend::Register(OrthancPluginContext* context,
654                                 StorageBackend* backend)
655   {
656     if (context == NULL ||
657         backend == NULL)
658     {
659       throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
660     }
661     else if (context_ != NULL ||
662              backend_.get() != NULL)
663     {
664       // This function can only be invoked once in the plugin
665       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
666     }
667     else
668     {
669       context_ = context;
670       backend_.reset(backend);
671 
672       bool hasLoadedV2 = false;
673 
674 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
675 #  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 0)
676       if (OrthancPluginCheckVersionAdvanced(context, 1, 9, 0) == 1)
677       {
678         OrthancPluginStorageReadRange readRange = NULL;
679         if (backend_->HasReadRange())
680         {
681           readRange = StorageReadRange;
682         }
683 
684         OrthancPluginRegisterStorageArea2(context_, StorageCreate, StorageReadWhole, readRange, StorageRemove);
685         hasLoadedV2 = true;
686       }
687 #  endif
688 #endif
689 
690       if (!hasLoadedV2)
691       {
692         LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support reading of file ranges";
693         OrthancPluginRegisterStorageArea(context_, StorageCreate, StorageRead, StorageRemove);
694       }
695 
696       LOG(WARNING) << "The storage area plugin will retry up to " << backend_->GetMaxRetries()
697                    << " time(s) in the case of a collision";
698     }
699   }
700 
701 
Finalize()702   void StorageBackend::Finalize()
703   {
704     backend_.reset(NULL);
705     context_ = NULL;
706   }
707 
708 
709   class StorageBackend::StringVisitor : public StorageBackend::IFileContentVisitor
710   {
711   private:
712     std::string&  target_;
713     bool          success_;
714 
715   public:
StringVisitor(std::string & target)716     explicit StringVisitor(std::string& target) :
717       target_(target),
718       success_(false)
719     {
720     }
721 
IsSuccess() const722     virtual bool IsSuccess() const ORTHANC_OVERRIDE
723     {
724       return success_;
725     }
726 
Assign(const std::string & content)727     virtual void Assign(const std::string& content) ORTHANC_OVERRIDE
728     {
729       if (success_)
730       {
731         throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
732       }
733       else
734       {
735         target_.assign(content);
736         success_ = true;
737       }
738     }
739   };
740 
741 
ReadWholeToString(std::string & target,IAccessor & accessor,const std::string & uuid,OrthancPluginContentType type)742   void StorageBackend::ReadWholeToString(std::string& target,
743                                          IAccessor& accessor,
744                                          const std::string& uuid,
745                                          OrthancPluginContentType type)
746   {
747     StringVisitor visitor(target);
748     accessor.ReadWhole(visitor, uuid, type);
749 
750     if (!visitor.IsSuccess())
751     {
752       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
753     }
754   }
755 
756 
ReadRangeToString(std::string & target,IAccessor & accessor,const std::string & uuid,OrthancPluginContentType type,uint64_t start,size_t length)757   void StorageBackend::ReadRangeToString(std::string& target,
758                                          IAccessor& accessor,
759                                          const std::string& uuid,
760                                          OrthancPluginContentType type,
761                                          uint64_t start,
762                                          size_t length)
763   {
764     StringVisitor visitor(target);
765     accessor.ReadRange(visitor, uuid, type, start, length);
766 
767     if (!visitor.IsSuccess())
768     {
769       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
770     }
771   }
772 
773 
Execute(IDatabaseOperation & operation)774   void StorageBackend::Execute(IDatabaseOperation& operation)
775   {
776     std::unique_ptr<IAccessor> accessor(CreateAccessor());
777     if (accessor.get() == NULL)
778     {
779       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
780     }
781 
782 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 2)
783     unsigned int attempt = 0;
784 #endif
785 
786     for (;;)
787     {
788       try
789       {
790         operation.Execute(*accessor);
791         return;  // Success
792       }
793       catch (Orthanc::OrthancException& e)
794       {
795 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 2)
796         if (e.GetErrorCode() == Orthanc::ErrorCode_DatabaseCannotSerialize)
797         {
798           if (attempt >= maxRetries_)
799           {
800             throw;
801           }
802           else
803           {
804             attempt++;
805 
806             // The "rand()" adds some jitter to de-synchronize writers
807             boost::this_thread::sleep(boost::posix_time::milliseconds(100 * attempt + 5 * (rand() % 10)));
808           }
809         }
810         else
811         {
812           throw;
813         }
814 #else
815         throw;
816 #endif
817       }
818     }
819   }
820 }
821