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 "ServerContext.h"
36 
37 #include "../../OrthancFramework/Sources/Cache/SharedArchive.h"
38 #include "../../OrthancFramework/Sources/DicomFormat/DicomElement.h"
39 #include "../../OrthancFramework/Sources/DicomFormat/DicomStreamReader.h"
40 #include "../../OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h"
41 #include "../../OrthancFramework/Sources/DicomParsing/DicomModification.h"
42 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
43 #include "../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h"
44 #include "../../OrthancFramework/Sources/FileStorage/StorageAccessor.h"
45 #include "../../OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h"
46 #include "../../OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.h"
47 #include "../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h"
48 #include "../../OrthancFramework/Sources/Logging.h"
49 #include "../../OrthancFramework/Sources/MetricsRegistry.h"
50 #include "../Plugins/Engine/OrthancPlugins.h"
51 
52 #include "OrthancConfiguration.h"
53 #include "OrthancRestApi/OrthancRestApi.h"
54 #include "Search/DatabaseLookup.h"
55 #include "ServerJobs/OrthancJobUnserializer.h"
56 #include "ServerToolbox.h"
57 #include "StorageCommitmentReports.h"
58 
59 #include <dcmtk/dcmdata/dcfilefo.h>
60 
61 
62 static size_t DICOM_CACHE_SIZE = 128 * 1024 * 1024;  // 128 MB
63 
64 
65 /**
66  * IMPORTANT: We make the assumption that the same instance of
67  * FileStorage can be accessed from multiple threads. This seems OK
68  * since the filesystem implements the required locking mechanisms,
69  * but maybe a read-writer lock on the "FileStorage" could be
70  * useful. Conversely, "ServerIndex" already implements mutex-based
71  * locking.
72  **/
73 
74 namespace Orthanc
75 {
IsUncompressedTransferSyntax(DicomTransferSyntax transferSyntax)76   static bool IsUncompressedTransferSyntax(DicomTransferSyntax transferSyntax)
77   {
78     return (transferSyntax == DicomTransferSyntax_LittleEndianImplicit ||
79             transferSyntax == DicomTransferSyntax_LittleEndianExplicit ||
80             transferSyntax == DicomTransferSyntax_BigEndianExplicit);
81   }
82 
83 
IsTranscodableTransferSyntax(DicomTransferSyntax transferSyntax)84   static bool IsTranscodableTransferSyntax(DicomTransferSyntax transferSyntax)
85   {
86     return (
87       // Do not try to transcode DICOM videos (new in Orthanc 1.8.2)
88       transferSyntax != DicomTransferSyntax_MPEG2MainProfileAtMainLevel &&
89       transferSyntax != DicomTransferSyntax_MPEG2MainProfileAtHighLevel &&
90       transferSyntax != DicomTransferSyntax_MPEG4HighProfileLevel4_1 &&
91       transferSyntax != DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1 &&
92       transferSyntax != DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo &&
93       transferSyntax != DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo &&
94       transferSyntax != DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2 &&
95       transferSyntax != DicomTransferSyntax_HEVCMainProfileLevel5_1 &&
96       transferSyntax != DicomTransferSyntax_HEVCMain10ProfileLevel5_1 &&
97 
98       // Do not try to transcode special transfer syntaxes
99       transferSyntax != DicomTransferSyntax_RFC2557MimeEncapsulation &&
100       transferSyntax != DicomTransferSyntax_XML);
101   }
102 
103 
ChangeThread(ServerContext * that,unsigned int sleepDelay)104   void ServerContext::ChangeThread(ServerContext* that,
105                                    unsigned int sleepDelay)
106   {
107     while (!that->done_)
108     {
109       std::unique_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay));
110 
111       if (obj.get() != NULL)
112       {
113         const ServerIndexChange& change = dynamic_cast<const ServerIndexChange&>(*obj.get());
114 
115         boost::shared_lock<boost::shared_mutex> lock(that->listenersMutex_);
116         for (ServerListeners::iterator it = that->listeners_.begin();
117              it != that->listeners_.end(); ++it)
118         {
119           try
120           {
121             try
122             {
123               it->GetListener().SignalChange(change);
124             }
125             catch (std::bad_alloc&)
126             {
127               LOG(ERROR) << "Not enough memory while signaling a change";
128             }
129             catch (...)
130             {
131               throw OrthancException(ErrorCode_InternalError);
132             }
133           }
134           catch (OrthancException& e)
135           {
136             LOG(ERROR) << "Error in the " << it->GetDescription()
137                        << " callback while signaling a change: " << e.What()
138                        << " (code " << e.GetErrorCode() << ")";
139           }
140         }
141       }
142     }
143   }
144 
145 
SaveJobsThread(ServerContext * that,unsigned int sleepDelay)146   void ServerContext::SaveJobsThread(ServerContext* that,
147                                      unsigned int sleepDelay)
148   {
149     static const boost::posix_time::time_duration PERIODICITY =
150       boost::posix_time::seconds(10);
151 
152     boost::posix_time::ptime next =
153       boost::posix_time::microsec_clock::universal_time() + PERIODICITY;
154 
155     while (!that->done_)
156     {
157       boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDelay));
158 
159       if (that->haveJobsChanged_ ||
160           boost::posix_time::microsec_clock::universal_time() >= next)
161       {
162         that->haveJobsChanged_ = false;
163         that->SaveJobsEngine();
164         next = boost::posix_time::microsec_clock::universal_time() + PERIODICITY;
165       }
166     }
167   }
168 
169 
SignalJobSubmitted(const std::string & jobId)170   void ServerContext::SignalJobSubmitted(const std::string& jobId)
171   {
172     haveJobsChanged_ = true;
173     mainLua_.SignalJobSubmitted(jobId);
174 
175 #if ORTHANC_ENABLE_PLUGINS == 1
176     if (HasPlugins())
177     {
178       GetPlugins().SignalJobSubmitted(jobId);
179     }
180 #endif
181   }
182 
183 
SignalJobSuccess(const std::string & jobId)184   void ServerContext::SignalJobSuccess(const std::string& jobId)
185   {
186     haveJobsChanged_ = true;
187     mainLua_.SignalJobSuccess(jobId);
188 
189 #if ORTHANC_ENABLE_PLUGINS == 1
190     if (HasPlugins())
191     {
192       GetPlugins().SignalJobSuccess(jobId);
193     }
194 #endif
195   }
196 
197 
SignalJobFailure(const std::string & jobId)198   void ServerContext::SignalJobFailure(const std::string& jobId)
199   {
200     haveJobsChanged_ = true;
201     mainLua_.SignalJobFailure(jobId);
202 
203 #if ORTHANC_ENABLE_PLUGINS == 1
204     if (HasPlugins())
205     {
206       GetPlugins().SignalJobFailure(jobId);
207     }
208 #endif
209   }
210 
211 
SetupJobsEngine(bool unitTesting,bool loadJobsFromDatabase)212   void ServerContext::SetupJobsEngine(bool unitTesting,
213                                       bool loadJobsFromDatabase)
214   {
215     if (loadJobsFromDatabase)
216     {
217       std::string serialized;
218       if (index_.LookupGlobalProperty(serialized, GlobalProperty_JobsRegistry, false /* not shared */))
219       {
220         LOG(WARNING) << "Reloading the jobs from the last execution of Orthanc";
221 
222         try
223         {
224           OrthancJobUnserializer unserializer(*this);
225           jobsEngine_.LoadRegistryFromString(unserializer, serialized);
226         }
227         catch (OrthancException& e)
228         {
229           LOG(WARNING) << "Cannot unserialize the jobs engine, starting anyway: " << e.What();
230         }
231       }
232       else
233       {
234         LOG(INFO) << "The last execution of Orthanc has archived no job";
235       }
236     }
237     else
238     {
239       LOG(INFO) << "Not reloading the jobs from the last execution of Orthanc";
240     }
241 
242     jobsEngine_.GetRegistry().SetObserver(*this);
243     jobsEngine_.Start();
244     isJobsEngineUnserialized_ = true;
245 
246     saveJobsThread_ = boost::thread(SaveJobsThread, this, (unitTesting ? 20 : 100));
247   }
248 
249 
SaveJobsEngine()250   void ServerContext::SaveJobsEngine()
251   {
252     if (saveJobs_)
253     {
254       LOG(TRACE) << "Serializing the content of the jobs engine";
255 
256       try
257       {
258         Json::Value value;
259         jobsEngine_.GetRegistry().Serialize(value);
260 
261         std::string serialized;
262         Toolbox::WriteFastJson(serialized, value);
263 
264         index_.SetGlobalProperty(GlobalProperty_JobsRegistry, false /* not shared */, serialized);
265       }
266       catch (OrthancException& e)
267       {
268         LOG(ERROR) << "Cannot serialize the jobs engine: " << e.What();
269       }
270     }
271   }
272 
273 
PublishDicomCacheMetrics()274   void ServerContext::PublishDicomCacheMetrics()
275   {
276     metricsRegistry_->SetValue("orthanc_dicom_cache_size",
277                                static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024));
278     metricsRegistry_->SetValue("orthanc_dicom_cache_count",
279                                static_cast<float>(dicomCache_.GetNumberOfItems()));
280   }
281 
282 
ServerContext(IDatabaseWrapper & database,IStorageArea & area,bool unitTesting,size_t maxCompletedJobs)283   ServerContext::ServerContext(IDatabaseWrapper& database,
284                                IStorageArea& area,
285                                bool unitTesting,
286                                size_t maxCompletedJobs) :
287     index_(*this, database, (unitTesting ? 20 : 500)),
288     area_(area),
289     compressionEnabled_(false),
290     storeMD5_(true),
291     largeDicomThrottler_(1),
292     dicomCache_(DICOM_CACHE_SIZE),
293     mainLua_(*this),
294     filterLua_(*this),
295     luaListener_(*this),
296     jobsEngine_(maxCompletedJobs),
297 #if ORTHANC_ENABLE_PLUGINS == 1
298     plugins_(NULL),
299 #endif
300     done_(false),
301     haveJobsChanged_(false),
302     isJobsEngineUnserialized_(false),
303     metricsRegistry_(new MetricsRegistry),
304     isHttpServerSecure_(true),
305     isExecuteLuaEnabled_(false),
306     overwriteInstances_(false),
307     dcmtkTranscoder_(new DcmtkTranscoder),
308     isIngestTranscoding_(false),
309     ingestTranscodingOfUncompressed_(true),
310     ingestTranscodingOfCompressed_(true),
311     preferredTransferSyntax_(DicomTransferSyntax_LittleEndianExplicit),
312     deidentifyLogs_(false)
313   {
314     try
315     {
316       unsigned int lossyQuality;
317 
318       {
319         OrthancConfiguration::ReaderLock lock;
320 
321         queryRetrieveArchive_.reset(
322           new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("QueryRetrieveSize", 100)));
323         mediaArchive_.reset(
324           new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("MediaArchiveSize", 1)));
325         defaultLocalAet_ = lock.GetConfiguration().GetOrthancAET();
326         jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2));
327         saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true);
328         metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true));
329 
330         // New configuration options in Orthanc 1.5.1
331         findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"));
332         limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
333         limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
334 
335         // New configuration option in Orthanc 1.6.0
336         storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100)));
337 
338         // New options in Orthanc 1.7.0
339         transcodeDicomProtocol_ = lock.GetConfiguration().GetBooleanParameter("TranscodeDicomProtocol", true);
340         builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder", "After"));
341         lossyQuality = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomLossyTranscodingQuality", 90);
342 
343         std::string s;
344         if (lock.GetConfiguration().LookupStringParameter(s, "IngestTranscoding"))
345         {
346           if (LookupTransferSyntax(ingestTransferSyntax_, s))
347           {
348             isIngestTranscoding_ = true;
349             LOG(WARNING) << "Incoming DICOM instances will automatically be transcoded to "
350                          << "transfer syntax: " << GetTransferSyntaxUid(ingestTransferSyntax_);
351 
352             // New options in Orthanc 1.8.2
353             ingestTranscodingOfUncompressed_ = lock.GetConfiguration().GetBooleanParameter("IngestTranscodingOfUncompressed", true);
354             ingestTranscodingOfCompressed_ = lock.GetConfiguration().GetBooleanParameter("IngestTranscodingOfCompressed", true);
355 
356             LOG(WARNING) << "  Ingest transcoding will "
357                          << (ingestTranscodingOfUncompressed_ ? "be applied" : "*not* be applied")
358                          << " to uncompressed transfer syntaxes (Little Endian Implicit/Explicit, Big Endian Explicit)";
359 
360             LOG(WARNING) << "  Ingest transcoding will "
361                          << (ingestTranscodingOfCompressed_ ? "be applied" : "*not* be applied")
362                          << " to compressed transfer syntaxes";
363           }
364           else
365           {
366             throw OrthancException(ErrorCode_ParameterOutOfRange,
367                                    "Unknown transfer syntax for ingest transcoding: " + s);
368           }
369         }
370         else
371         {
372           isIngestTranscoding_ = false;
373           LOG(INFO) << "Automated transcoding of incoming DICOM instances is disabled";
374         }
375 
376         // New options in Orthanc 1.8.2
377         if (lock.GetConfiguration().GetBooleanParameter("DeidentifyLogs", true))
378         {
379           deidentifyLogs_ = true;
380           CLOG(INFO, DICOM) << "Deidentification of log contents (notably for DIMSE queries) is enabled";
381 
382           DicomVersion version = StringToDicomVersion(
383               lock.GetConfiguration().GetStringParameter("DeidentifyLogsDicomVersion", "2017c"));
384           CLOG(INFO, DICOM) << "Version of DICOM standard used for deidentification is "
385                             << EnumerationToString(version);
386 
387           logsDeidentifierRules_.SetupAnonymization(version);
388         }
389         else
390         {
391           deidentifyLogs_ = false;
392           CLOG(INFO, DICOM) << "Deidentification of log contents (notably for DIMSE queries) is disabled";
393         }
394 
395         // New options in Orthanc 1.9.0
396         if (lock.GetConfiguration().LookupStringParameter(s, "DicomScuPreferredTransferSyntax") &&
397             !LookupTransferSyntax(preferredTransferSyntax_, s))
398         {
399           throw OrthancException(ErrorCode_ParameterOutOfRange,
400                                  "Unknown preferred transfer syntax: " + s);
401         }
402 
403         CLOG(INFO, DICOM) << "Preferred transfer syntax for Orthanc C-STORE SCU: "
404                           << GetTransferSyntaxUid(preferredTransferSyntax_);
405 
406         lock.GetConfiguration().GetAcceptedTransferSyntaxes(acceptedTransferSyntaxes_);
407 
408         isUnknownSopClassAccepted_ = lock.GetConfiguration().GetBooleanParameter("UnknownSopClassAccepted", false);
409       }
410 
411       jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
412 
413       listeners_.push_back(ServerListener(luaListener_, "Lua"));
414       changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100));
415 
416       dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetLossyQuality(lossyQuality);
417     }
418     catch (OrthancException&)
419     {
420       Stop();
421       throw;
422     }
423   }
424 
425 
426 
~ServerContext()427   ServerContext::~ServerContext()
428   {
429     if (!done_)
430     {
431       LOG(ERROR) << "INTERNAL ERROR: ServerContext::Stop() should be invoked manually to avoid mess in the destruction order!";
432       Stop();
433     }
434   }
435 
436 
Stop()437   void ServerContext::Stop()
438   {
439     if (!done_)
440     {
441       {
442         boost::unique_lock<boost::shared_mutex> lock(listenersMutex_);
443         listeners_.clear();
444       }
445 
446       done_ = true;
447 
448       if (changeThread_.joinable())
449       {
450         changeThread_.join();
451       }
452 
453       if (saveJobsThread_.joinable())
454       {
455         saveJobsThread_.join();
456       }
457 
458       jobsEngine_.GetRegistry().ResetObserver();
459 
460       if (isJobsEngineUnserialized_)
461       {
462         // Avoid losing jobs if the JobsRegistry cannot be unserialized
463         SaveJobsEngine();
464       }
465 
466       // Do not change the order below!
467       jobsEngine_.Stop();
468       index_.Stop();
469     }
470   }
471 
472 
SetCompressionEnabled(bool enabled)473   void ServerContext::SetCompressionEnabled(bool enabled)
474   {
475     if (enabled)
476       LOG(WARNING) << "Disk compression is enabled";
477     else
478       LOG(WARNING) << "Disk compression is disabled";
479 
480     compressionEnabled_ = enabled;
481   }
482 
483 
RemoveFile(const std::string & fileUuid,FileContentType type)484   void ServerContext::RemoveFile(const std::string& fileUuid,
485                                  FileContentType type)
486   {
487     StorageAccessor accessor(area_, GetMetricsRegistry());
488     accessor.Remove(fileUuid, type);
489   }
490 
491 
StoreAfterTranscoding(std::string & resultPublicId,DicomInstanceToStore & dicom,StoreInstanceMode mode)492   StoreStatus ServerContext::StoreAfterTranscoding(std::string& resultPublicId,
493                                                    DicomInstanceToStore& dicom,
494                                                    StoreInstanceMode mode)
495   {
496     bool overwrite;
497     switch (mode)
498     {
499       case StoreInstanceMode_Default:
500         overwrite = overwriteInstances_;
501         break;
502 
503       case StoreInstanceMode_OverwriteDuplicate:
504         overwrite = true;
505         break;
506 
507       case StoreInstanceMode_IgnoreDuplicate:
508         overwrite = false;
509         break;
510 
511       default:
512         throw OrthancException(ErrorCode_ParameterOutOfRange);
513     }
514 
515     bool hasPixelDataOffset;
516     uint64_t pixelDataOffset;
517     hasPixelDataOffset = DicomStreamReader::LookupPixelDataOffset(
518       pixelDataOffset, dicom.GetBufferData(), dicom.GetBufferSize());
519 
520     DicomTransferSyntax transferSyntax;
521     bool hasTransferSyntax = dicom.LookupTransferSyntax(transferSyntax);
522 
523     DicomMap summary;
524     dicom.GetSummary(summary);
525 
526     try
527     {
528       MetricsRegistry::Timer timer(GetMetricsRegistry(), "orthanc_store_dicom_duration_ms");
529       StorageAccessor accessor(area_, GetMetricsRegistry());
530 
531       DicomInstanceHasher hasher(summary);
532       resultPublicId = hasher.HashInstance();
533 
534       Json::Value dicomAsJson;
535       dicom.GetDicomAsJson(dicomAsJson);
536 
537       Json::Value simplifiedTags;
538       Toolbox::SimplifyDicomAsJson(simplifiedTags, dicomAsJson, DicomToJsonFormat_Human);
539 
540       // Test if the instance must be filtered out
541       bool accepted = true;
542 
543       {
544         boost::shared_lock<boost::shared_mutex> lock(listenersMutex_);
545 
546         for (ServerListeners::iterator it = listeners_.begin(); it != listeners_.end(); ++it)
547         {
548           try
549           {
550             if (!it->GetListener().FilterIncomingInstance(dicom, simplifiedTags))
551             {
552               accepted = false;
553               break;
554             }
555           }
556           catch (OrthancException& e)
557           {
558             LOG(ERROR) << "Error in the " << it->GetDescription()
559                        << " callback while receiving an instance: " << e.What()
560                        << " (code " << e.GetErrorCode() << ")";
561             throw;
562           }
563         }
564       }
565 
566       if (!accepted)
567       {
568         LOG(INFO) << "An incoming instance has been discarded by the filter";
569         return StoreStatus_FilteredOut;
570       }
571 
572       // Remove the file from the DicomCache (useful if
573       // "OverwriteInstances" is set to "true")
574       dicomCache_.Invalidate(resultPublicId);
575       PublishDicomCacheMetrics();
576 
577       // TODO Should we use "gzip" instead?
578       CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None);
579 
580       FileInfo dicomInfo = accessor.Write(dicom.GetBufferData(), dicom.GetBufferSize(),
581                                           FileContentType_Dicom, compression, storeMD5_);
582 
583       ServerIndex::Attachments attachments;
584       attachments.push_back(dicomInfo);
585 
586       FileInfo dicomUntilPixelData;
587       if (hasPixelDataOffset &&
588           (!area_.HasReadRange() ||
589            compressionEnabled_))
590       {
591         dicomUntilPixelData = accessor.Write(dicom.GetBufferData(), pixelDataOffset,
592                                              FileContentType_DicomUntilPixelData, compression, storeMD5_);
593         attachments.push_back(dicomUntilPixelData);
594       }
595 
596       typedef std::map<MetadataType, std::string>  InstanceMetadata;
597       InstanceMetadata  instanceMetadata;
598       StoreStatus status = index_.Store(
599         instanceMetadata, summary, attachments, dicom.GetMetadata(), dicom.GetOrigin(), overwrite,
600         hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset);
601 
602       // Only keep the metadata for the "instance" level
603       dicom.ClearMetadata();
604 
605       for (InstanceMetadata::const_iterator it = instanceMetadata.begin();
606            it != instanceMetadata.end(); ++it)
607       {
608         dicom.AddMetadata(ResourceType_Instance, it->first, it->second);
609       }
610 
611       if (status != StoreStatus_Success)
612       {
613         accessor.Remove(dicomInfo);
614 
615         if (dicomUntilPixelData.IsValid())
616         {
617           accessor.Remove(dicomUntilPixelData);
618         }
619       }
620 
621       switch (status)
622       {
623         case StoreStatus_Success:
624           LOG(INFO) << "New instance stored";
625           break;
626 
627         case StoreStatus_AlreadyStored:
628           LOG(INFO) << "Already stored";
629           break;
630 
631         case StoreStatus_Failure:
632           LOG(ERROR) << "Store failure";
633           break;
634 
635         default:
636           // This should never happen
637           break;
638       }
639 
640       if (status == StoreStatus_Success ||
641           status == StoreStatus_AlreadyStored)
642       {
643         boost::shared_lock<boost::shared_mutex> lock(listenersMutex_);
644 
645         for (ServerListeners::iterator it = listeners_.begin(); it != listeners_.end(); ++it)
646         {
647           try
648           {
649             it->GetListener().SignalStoredInstance(resultPublicId, dicom, simplifiedTags);
650           }
651           catch (OrthancException& e)
652           {
653             LOG(ERROR) << "Error in the " << it->GetDescription()
654                        << " callback while receiving an instance: " << e.What()
655                        << " (code " << e.GetErrorCode() << ")";
656           }
657         }
658       }
659 
660       return status;
661     }
662     catch (OrthancException& e)
663     {
664       if (e.GetErrorCode() == ErrorCode_InexistentTag)
665       {
666         summary.LogMissingTagsForStore();
667       }
668 
669       throw;
670     }
671   }
672 
673 
Store(std::string & resultPublicId,DicomInstanceToStore & dicom,StoreInstanceMode mode)674   StoreStatus ServerContext::Store(std::string& resultPublicId,
675                                    DicomInstanceToStore& dicom,
676                                    StoreInstanceMode mode)
677   {
678     if (!isIngestTranscoding_)
679     {
680       // No automated transcoding. This was the only path in Orthanc <= 1.6.1.
681       return StoreAfterTranscoding(resultPublicId, dicom, mode);
682     }
683     else
684     {
685       // Automated transcoding of incoming DICOM instance
686 
687       bool transcode = false;
688 
689       DicomTransferSyntax sourceSyntax;
690       if (!dicom.LookupTransferSyntax(sourceSyntax) ||
691           sourceSyntax == ingestTransferSyntax_)
692       {
693         // Don't transcode if the incoming DICOM is already in the proper transfer syntax
694         transcode = false;
695       }
696       else if (!IsTranscodableTransferSyntax(sourceSyntax))
697       {
698         // Don't try to transcode video files, this is useless (new in
699         // Orthanc 1.8.2). This could be accepted in the future if
700         // video transcoding gets implemented.
701         transcode = false;
702       }
703       else if (IsUncompressedTransferSyntax(sourceSyntax))
704       {
705         // This is an uncompressed transfer syntax (new in Orthanc 1.8.2)
706         transcode = ingestTranscodingOfUncompressed_;
707       }
708       else
709       {
710         // This is an compressed transfer syntax (new in Orthanc 1.8.2)
711         transcode = ingestTranscodingOfCompressed_;
712       }
713 
714       if (!transcode)
715       {
716         // No transcoding
717         return StoreAfterTranscoding(resultPublicId, dicom, mode);
718       }
719       else
720       {
721         // Trancoding
722         std::set<DicomTransferSyntax> syntaxes;
723         syntaxes.insert(ingestTransferSyntax_);
724 
725         IDicomTranscoder::DicomImage source;
726         source.SetExternalBuffer(dicom.GetBufferData(), dicom.GetBufferSize());
727 
728         IDicomTranscoder::DicomImage transcoded;
729         if (Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */))
730         {
731           std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile());
732 
733           std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*tmp));
734           toStore->SetOrigin(dicom.GetOrigin());
735 
736           StoreStatus ok = StoreAfterTranscoding(resultPublicId, *toStore, mode);
737           assert(resultPublicId == tmp->GetHasher().HashInstance());
738 
739           return ok;
740         }
741         else
742         {
743           // Cannot transcode => store the original file
744           return StoreAfterTranscoding(resultPublicId, dicom, mode);
745         }
746       }
747     }
748   }
749 
750 
AnswerAttachment(RestApiOutput & output,const std::string & resourceId,FileContentType content)751   void ServerContext::AnswerAttachment(RestApiOutput& output,
752                                        const std::string& resourceId,
753                                        FileContentType content)
754   {
755     FileInfo attachment;
756     int64_t revision;
757     if (!index_.LookupAttachment(attachment, revision, resourceId, content))
758     {
759       throw OrthancException(ErrorCode_UnknownResource);
760     }
761     else
762     {
763       StorageAccessor accessor(area_, GetMetricsRegistry());
764       accessor.AnswerFile(output, attachment, GetFileContentMime(content));
765     }
766   }
767 
768 
ChangeAttachmentCompression(const std::string & resourceId,FileContentType attachmentType,CompressionType compression)769   void ServerContext::ChangeAttachmentCompression(const std::string& resourceId,
770                                                   FileContentType attachmentType,
771                                                   CompressionType compression)
772   {
773     LOG(INFO) << "Changing compression type for attachment "
774               << EnumerationToString(attachmentType)
775               << " of resource " << resourceId << " to "
776               << compression;
777 
778     FileInfo attachment;
779     int64_t revision;
780     if (!index_.LookupAttachment(attachment, revision, resourceId, attachmentType))
781     {
782       throw OrthancException(ErrorCode_UnknownResource);
783     }
784 
785     if (attachment.GetCompressionType() == compression)
786     {
787       // Nothing to do
788       return;
789     }
790 
791     std::string content;
792 
793     StorageAccessor accessor(area_, GetMetricsRegistry());
794     accessor.Read(content, attachment);
795 
796     FileInfo modified = accessor.Write(content.empty() ? NULL : content.c_str(),
797                                        content.size(), attachmentType, compression, storeMD5_);
798 
799     try
800     {
801       int64_t newRevision;  // ignored
802       StoreStatus status = index_.AddAttachment(newRevision, modified, resourceId,
803                                                 true, revision, modified.GetUncompressedMD5());
804       if (status != StoreStatus_Success)
805       {
806         accessor.Remove(modified);
807         throw OrthancException(ErrorCode_Database);
808       }
809     }
810     catch (OrthancException&)
811     {
812       accessor.Remove(modified);
813       throw;
814     }
815   }
816 
817 
InjectEmptyPixelData(Json::Value & dicomAsJson)818   static void InjectEmptyPixelData(Json::Value& dicomAsJson)
819   {
820     // This is for backward compatibility with Orthanc <= 1.9.0
821     Json::Value pixelData = Json::objectValue;
822     pixelData["Name"] = "PixelData";
823     pixelData["Type"] = "Null";
824     pixelData["Value"] = Json::nullValue;
825 
826     dicomAsJson["7fe0,0010"] = pixelData;
827   }
828 
829 
ReadDicomAsJson(Json::Value & result,const std::string & instancePublicId,const std::set<DicomTag> & ignoreTagLength)830   void ServerContext::ReadDicomAsJson(Json::Value& result,
831                                       const std::string& instancePublicId,
832                                       const std::set<DicomTag>& ignoreTagLength)
833   {
834     /**
835      * CASE 1: The DICOM file, truncated at pixel data, is available
836      * as an attachment (it was created either because the storage
837      * area does not support range reads, or it "StorageCompression"
838      * is enabled). Simply return this attachment.
839      **/
840 
841     FileInfo attachment;
842     int64_t revision;  // Ignored
843 
844     if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData))
845     {
846       std::string dicom;
847 
848       {
849         StorageAccessor accessor(area_, GetMetricsRegistry());
850         accessor.Read(dicom, attachment);
851       }
852 
853       ParsedDicomFile parsed(dicom);
854       OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
855       InjectEmptyPixelData(result);
856     }
857     else
858     {
859       /**
860        * The truncated DICOM file is not stored as a standalone
861        * attachment. Lookup whether the pixel data offset has already
862        * been computed for this instance.
863        **/
864 
865       bool hasPixelDataOffset;
866       uint64_t pixelDataOffset = 0;  // dummy initialization
867 
868       {
869         std::string s;
870         if (index_.LookupMetadata(s, revision, instancePublicId, ResourceType_Instance,
871                                   MetadataType_Instance_PixelDataOffset))
872         {
873           hasPixelDataOffset = false;
874 
875           if (!s.empty())
876           {
877             try
878             {
879               pixelDataOffset = boost::lexical_cast<uint64_t>(s);
880               hasPixelDataOffset = true;
881             }
882             catch (boost::bad_lexical_cast&)
883             {
884             }
885           }
886 
887           if (!hasPixelDataOffset)
888           {
889             LOG(ERROR) << "Metadata \"PixelDataOffset\" is corrupted for instance: " << instancePublicId;
890           }
891         }
892         else
893         {
894           // This instance was created by a version of Orthanc <= 1.9.0
895           hasPixelDataOffset = false;
896         }
897       }
898 
899 
900       if (hasPixelDataOffset &&
901           area_.HasReadRange() &&
902           index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom) &&
903           attachment.GetCompressionType() == CompressionType_None)
904       {
905         /**
906          * CASE 2: The pixel data offset is known, AND that a range read
907          * can be used to retrieve the truncated DICOM file. Note that
908          * this case cannot be used if "StorageCompression" option is
909          * "true".
910          **/
911 
912         std::unique_ptr<IMemoryBuffer> dicom;
913         {
914           MetricsRegistry::Timer timer(GetMetricsRegistry(), "orthanc_storage_read_range_duration_ms");
915           dicom.reset(area_.ReadRange(attachment.GetUuid(), FileContentType_Dicom, 0, pixelDataOffset));
916         }
917 
918         if (dicom.get() == NULL)
919         {
920           throw OrthancException(ErrorCode_InternalError);
921         }
922         else
923         {
924           assert(dicom->GetSize() == pixelDataOffset);
925           ParsedDicomFile parsed(dicom->GetData(), dicom->GetSize());
926           OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
927           InjectEmptyPixelData(result);
928         }
929       }
930       else if (ignoreTagLength.empty() &&
931                index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomAsJson))
932       {
933         /**
934          * CASE 3: This instance was created using Orthanc <=
935          * 1.9.0. Reuse the old "DICOM-as-JSON" attachment if available.
936          * This is for backward compatibility: A call to
937          * "/tools/invalidate-tags" or to one flavors of
938          * "/.../.../reconstruct" will disable this case.
939          **/
940 
941         std::string dicomAsJson;
942 
943         {
944           StorageAccessor accessor(area_, GetMetricsRegistry());
945           accessor.Read(dicomAsJson, attachment);
946         }
947 
948         if (!Toolbox::ReadJson(result, dicomAsJson))
949         {
950           throw OrthancException(ErrorCode_CorruptedFile,
951                                  "Corrupted DICOM-as-JSON attachment of instance: " + instancePublicId);
952         }
953       }
954       else
955       {
956         /**
957          * CASE 4: Neither the truncated DICOM file is accessible, nor
958          * the DICOM-as-JSON summary. We have to retrieve the full DICOM
959          * file from the storage area.
960          **/
961 
962         std::string dicom;
963         ReadDicom(dicom, instancePublicId);
964 
965         ParsedDicomFile parsed(dicom);
966         OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
967 
968         if (!hasPixelDataOffset)
969         {
970           /**
971            * The pixel data offset was never computed for this
972            * instance, which indicates that it was created using
973            * Orthanc <= 1.9.0, or that calls to
974            * "LookupPixelDataOffset()" from earlier versions of
975            * Orthanc have failed. Try again this precomputation now
976            * for future calls.
977            **/
978           if (DicomStreamReader::LookupPixelDataOffset(pixelDataOffset, dicom) &&
979               pixelDataOffset < dicom.size())
980           {
981             index_.OverwriteMetadata(instancePublicId, MetadataType_Instance_PixelDataOffset,
982                                      boost::lexical_cast<std::string>(pixelDataOffset));
983 
984             if (!area_.HasReadRange() ||
985                 compressionEnabled_)
986             {
987               int64_t newRevision;
988               AddAttachment(newRevision, instancePublicId, FileContentType_DicomUntilPixelData,
989                             dicom.empty() ? NULL: dicom.c_str(), pixelDataOffset,
990                             false /* no old revision */, -1 /* dummy revision */, "" /* dummy MD5 */);
991             }
992           }
993         }
994       }
995     }
996   }
997 
998 
ReadDicomAsJson(Json::Value & result,const std::string & instancePublicId)999   void ServerContext::ReadDicomAsJson(Json::Value& result,
1000                                       const std::string& instancePublicId)
1001   {
1002     std::set<DicomTag> ignoreTagLength;
1003     ReadDicomAsJson(result, instancePublicId, ignoreTagLength);
1004   }
1005 
1006 
ReadDicom(std::string & dicom,const std::string & instancePublicId)1007   void ServerContext::ReadDicom(std::string& dicom,
1008                                 const std::string& instancePublicId)
1009   {
1010     int64_t revision;
1011     ReadAttachment(dicom, revision, instancePublicId, FileContentType_Dicom, true /* uncompress */);
1012   }
1013 
1014 
ReadDicomUntilPixelData(std::string & dicom,const std::string & instancePublicId)1015   bool ServerContext::ReadDicomUntilPixelData(std::string& dicom,
1016                                               const std::string& instancePublicId)
1017   {
1018     if (!area_.HasReadRange())
1019     {
1020       return false;
1021     }
1022 
1023     FileInfo attachment;
1024     int64_t revision;  // Ignored
1025     if (!index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom))
1026     {
1027       throw OrthancException(ErrorCode_InternalError,
1028                              "Unable to read the DICOM file of instance " + instancePublicId);
1029     }
1030 
1031     std::string s;
1032 
1033     if (attachment.GetCompressionType() == CompressionType_None &&
1034         index_.LookupMetadata(s, revision, instancePublicId, ResourceType_Instance,
1035                               MetadataType_Instance_PixelDataOffset) &&
1036         !s.empty())
1037     {
1038       try
1039       {
1040         uint64_t pixelDataOffset = boost::lexical_cast<uint64_t>(s);
1041 
1042         std::unique_ptr<IMemoryBuffer> buffer(
1043           area_.ReadRange(attachment.GetUuid(), attachment.GetContentType(), 0, pixelDataOffset));
1044         buffer->MoveToString(dicom);
1045         return true;   // Success
1046       }
1047       catch (boost::bad_lexical_cast&)
1048       {
1049         LOG(ERROR) << "Metadata \"PixelDataOffset\" is corrupted for instance: " << instancePublicId;
1050       }
1051     }
1052 
1053     return false;
1054   }
1055 
1056 
ReadAttachment(std::string & result,int64_t & revision,const std::string & instancePublicId,FileContentType content,bool uncompressIfNeeded)1057   void ServerContext::ReadAttachment(std::string& result,
1058                                      int64_t& revision,
1059                                      const std::string& instancePublicId,
1060                                      FileContentType content,
1061                                      bool uncompressIfNeeded)
1062   {
1063     FileInfo attachment;
1064     if (!index_.LookupAttachment(attachment, revision, instancePublicId, content))
1065     {
1066       throw OrthancException(ErrorCode_InternalError,
1067                              "Unable to read attachment " + EnumerationToString(content) +
1068                              " of instance " + instancePublicId);
1069     }
1070 
1071     assert(attachment.GetContentType() == content);
1072 
1073     {
1074       StorageAccessor accessor(area_, GetMetricsRegistry());
1075 
1076       if (uncompressIfNeeded)
1077       {
1078         accessor.Read(result, attachment);
1079       }
1080       else
1081       {
1082         // Do not uncompress the content of the storage area, return the
1083         // raw data
1084         accessor.ReadRaw(result, attachment);
1085       }
1086     }
1087   }
1088 
1089 
DicomCacheLocker(ServerContext & context,const std::string & instancePublicId)1090   ServerContext::DicomCacheLocker::DicomCacheLocker(ServerContext& context,
1091                                                     const std::string& instancePublicId) :
1092     context_(context),
1093     instancePublicId_(instancePublicId)
1094   {
1095     accessor_.reset(new ParsedDicomCache::Accessor(context_.dicomCache_, instancePublicId));
1096 
1097     if (!accessor_->IsValid())
1098     {
1099       accessor_.reset(NULL);
1100 
1101       // Throttle to avoid loading several large DICOM files simultaneously
1102       largeDicomLocker_.reset(new Semaphore::Locker(context.largeDicomThrottler_));
1103 
1104       std::string content;
1105       context_.ReadDicom(content, instancePublicId);
1106 
1107       // Release the throttle if loading "small" DICOM files (under
1108       // 50MB, which is an arbitrary value)
1109       if (content.size() < 50 * 1024 * 1024)
1110       {
1111         largeDicomLocker_.reset(NULL);
1112       }
1113 
1114       dicom_.reset(new ParsedDicomFile(content));
1115       dicomSize_ = content.size();
1116     }
1117 
1118     assert(accessor_.get() != NULL ||
1119            dicom_.get() != NULL);
1120   }
1121 
1122 
~DicomCacheLocker()1123   ServerContext::DicomCacheLocker::~DicomCacheLocker()
1124   {
1125     if (dicom_.get() != NULL)
1126     {
1127       try
1128       {
1129         context_.dicomCache_.Acquire(instancePublicId_, dicom_.release(), dicomSize_);
1130         context_.PublishDicomCacheMetrics();
1131       }
1132       catch (OrthancException&)
1133       {
1134       }
1135     }
1136   }
1137 
1138 
GetDicom() const1139   ParsedDicomFile& ServerContext::DicomCacheLocker::GetDicom() const
1140   {
1141     if (dicom_.get() != NULL)
1142     {
1143       return *dicom_;
1144     }
1145     else
1146     {
1147       assert(accessor_.get() != NULL);
1148       return accessor_->GetDicom();
1149     }
1150   }
1151 
1152 
SetStoreMD5ForAttachments(bool storeMD5)1153   void ServerContext::SetStoreMD5ForAttachments(bool storeMD5)
1154   {
1155     LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no");
1156     storeMD5_ = storeMD5;
1157   }
1158 
1159 
AddAttachment(int64_t & newRevision,const std::string & resourceId,FileContentType attachmentType,const void * data,size_t size,bool hasOldRevision,int64_t oldRevision,const std::string & oldMD5)1160   bool ServerContext::AddAttachment(int64_t& newRevision,
1161                                     const std::string& resourceId,
1162                                     FileContentType attachmentType,
1163                                     const void* data,
1164                                     size_t size,
1165                                     bool hasOldRevision,
1166                                     int64_t oldRevision,
1167                                     const std::string& oldMD5)
1168   {
1169     LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId;
1170 
1171     // TODO Should we use "gzip" instead?
1172     CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None);
1173 
1174     StorageAccessor accessor(area_, GetMetricsRegistry());
1175     FileInfo attachment = accessor.Write(data, size, attachmentType, compression, storeMD5_);
1176 
1177     StoreStatus status = index_.AddAttachment(
1178       newRevision, attachment, resourceId, hasOldRevision, oldRevision, oldMD5);
1179     if (status != StoreStatus_Success)
1180     {
1181       accessor.Remove(attachment);
1182       return false;
1183     }
1184     else
1185     {
1186       return true;
1187     }
1188   }
1189 
1190 
DeleteResource(Json::Value & target,const std::string & uuid,ResourceType expectedType)1191   bool ServerContext::DeleteResource(Json::Value& target,
1192                                      const std::string& uuid,
1193                                      ResourceType expectedType)
1194   {
1195     if (expectedType == ResourceType_Instance)
1196     {
1197       // remove the file from the DicomCache
1198       dicomCache_.Invalidate(uuid);
1199       PublishDicomCacheMetrics();
1200     }
1201 
1202     return index_.DeleteResource(target, uuid, expectedType);
1203   }
1204 
1205 
SignalChange(const ServerIndexChange & change)1206   void ServerContext::SignalChange(const ServerIndexChange& change)
1207   {
1208     if (change.GetResourceType() == ResourceType_Instance &&
1209         change.GetChangeType() == ChangeType_Deleted)
1210     {
1211       dicomCache_.Invalidate(change.GetPublicId());
1212       PublishDicomCacheMetrics();
1213     }
1214 
1215     pendingChanges_.Enqueue(change.Clone());
1216   }
1217 
1218 
1219 #if ORTHANC_ENABLE_PLUGINS == 1
SetPlugins(OrthancPlugins & plugins)1220   void ServerContext::SetPlugins(OrthancPlugins& plugins)
1221   {
1222     boost::unique_lock<boost::shared_mutex> lock(listenersMutex_);
1223 
1224     plugins_ = &plugins;
1225 
1226     // TODO REFACTOR THIS
1227     listeners_.clear();
1228     listeners_.push_back(ServerListener(luaListener_, "Lua"));
1229     listeners_.push_back(ServerListener(plugins, "plugin"));
1230   }
1231 
1232 
ResetPlugins()1233   void ServerContext::ResetPlugins()
1234   {
1235     boost::unique_lock<boost::shared_mutex> lock(listenersMutex_);
1236 
1237     plugins_ = NULL;
1238 
1239     // TODO REFACTOR THIS
1240     listeners_.clear();
1241     listeners_.push_back(ServerListener(luaListener_, "Lua"));
1242   }
1243 
1244 
GetPlugins() const1245   const OrthancPlugins& ServerContext::GetPlugins() const
1246   {
1247     if (HasPlugins())
1248     {
1249       return *plugins_;
1250     }
1251     else
1252     {
1253       throw OrthancException(ErrorCode_InternalError);
1254     }
1255   }
1256 
GetPlugins()1257   OrthancPlugins& ServerContext::GetPlugins()
1258   {
1259     if (HasPlugins())
1260     {
1261       return *plugins_;
1262     }
1263     else
1264     {
1265       throw OrthancException(ErrorCode_InternalError);
1266     }
1267   }
1268 
1269 #endif
1270 
1271 
HasPlugins() const1272   bool ServerContext::HasPlugins() const
1273   {
1274 #if ORTHANC_ENABLE_PLUGINS == 1
1275     return (plugins_ != NULL);
1276 #else
1277     return false;
1278 #endif
1279   }
1280 
1281 
ApplyInternal(ILookupVisitor & visitor,const DatabaseLookup & lookup,ResourceType queryLevel,size_t since,size_t limit)1282   void ServerContext::ApplyInternal(ILookupVisitor& visitor,
1283                                     const DatabaseLookup& lookup,
1284                                     ResourceType queryLevel,
1285                                     size_t since,
1286                                     size_t limit)
1287   {
1288     unsigned int databaseLimit = (queryLevel == ResourceType_Instance ?
1289                                   limitFindInstances_ : limitFindResults_);
1290 
1291     std::vector<std::string> resources, instances;
1292 
1293     {
1294       const size_t lookupLimit = (databaseLimit == 0 ? 0 : databaseLimit + 1);
1295       GetIndex().ApplyLookupResources(resources, &instances, lookup, queryLevel, lookupLimit);
1296     }
1297 
1298     bool complete = (databaseLimit == 0 ||
1299                      resources.size() <= databaseLimit);
1300 
1301     LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size();
1302 
1303     /**
1304      * "resources" contains the Orthanc ID of the resource at level
1305      * "queryLevel", "instances" contains one the Orthanc ID of one
1306      * sample instance from this resource.
1307      **/
1308     assert(resources.size() == instances.size());
1309 
1310     size_t countResults = 0;
1311     size_t skipped = 0;
1312 
1313     const bool isDicomAsJsonNeeded = visitor.IsDicomAsJsonNeeded();
1314 
1315     for (size_t i = 0; i < instances.size(); i++)
1316     {
1317       // Optimization in Orthanc 1.5.1 - Don't read the full JSON from
1318       // the disk if only "main DICOM tags" are to be returned
1319 
1320       std::unique_ptr<Json::Value> dicomAsJson;
1321 
1322       bool hasOnlyMainDicomTags;
1323       DicomMap dicom;
1324 
1325       if (findStorageAccessMode_ == FindStorageAccessMode_DatabaseOnly ||
1326           findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer ||
1327           lookup.HasOnlyMainDicomTags())
1328       {
1329         // Case (1): The main DICOM tags, as stored in the database,
1330         // are sufficient to look for match
1331 
1332         DicomMap tmp;
1333         if (!GetIndex().GetAllMainDicomTags(tmp, instances[i]))
1334         {
1335           // The instance has been removed during the execution of the
1336           // lookup, ignore it
1337           continue;
1338         }
1339 
1340         // New in Orthanc 1.6.0: Only keep the main DICOM tags at the
1341         // level of interest for the query
1342         switch (queryLevel)
1343         {
1344           // WARNING: Don't reorder cases below, and don't add "break"
1345           case ResourceType_Instance:
1346             dicom.MergeMainDicomTags(tmp, ResourceType_Instance);
1347 
1348           case ResourceType_Series:
1349             dicom.MergeMainDicomTags(tmp, ResourceType_Series);
1350 
1351           case ResourceType_Study:
1352             dicom.MergeMainDicomTags(tmp, ResourceType_Study);
1353 
1354           case ResourceType_Patient:
1355             dicom.MergeMainDicomTags(tmp, ResourceType_Patient);
1356             break;
1357 
1358           default:
1359             throw OrthancException(ErrorCode_InternalError);
1360         }
1361 
1362         hasOnlyMainDicomTags = true;
1363       }
1364       else
1365       {
1366         // Case (2): Need to read the "DICOM-as-JSON" attachment from
1367         // the storage area
1368         dicomAsJson.reset(new Json::Value);
1369         ReadDicomAsJson(*dicomAsJson, instances[i]);
1370 
1371         dicom.FromDicomAsJson(*dicomAsJson);
1372 
1373         // This map contains the entire JSON, i.e. more than the main DICOM tags
1374         hasOnlyMainDicomTags = false;
1375       }
1376 
1377       if (lookup.IsMatch(dicom))
1378       {
1379         if (skipped < since)
1380         {
1381           skipped++;
1382         }
1383         else if (limit != 0 &&
1384                  countResults >= limit)
1385         {
1386           // Too many results, don't mark as complete
1387           complete = false;
1388           break;
1389         }
1390         else
1391         {
1392           if ((findStorageAccessMode_ == FindStorageAccessMode_DiskOnLookupAndAnswer ||
1393                findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer) &&
1394               dicomAsJson.get() == NULL &&
1395               isDicomAsJsonNeeded)
1396           {
1397             dicomAsJson.reset(new Json::Value);
1398             ReadDicomAsJson(*dicomAsJson, instances[i]);
1399           }
1400 
1401           if (hasOnlyMainDicomTags)
1402           {
1403             // This is Case (1): The variable "dicom" only contains the main DICOM tags
1404             visitor.Visit(resources[i], instances[i], dicom, dicomAsJson.get());
1405           }
1406           else
1407           {
1408             // Remove the non-main DICOM tags from "dicom" if Case (2)
1409             // was used, for consistency with Case (1)
1410 
1411             DicomMap mainDicomTags;
1412             mainDicomTags.ExtractMainDicomTags(dicom);
1413             visitor.Visit(resources[i], instances[i], mainDicomTags, dicomAsJson.get());
1414           }
1415 
1416           countResults ++;
1417         }
1418       }
1419     }
1420 
1421     if (complete)
1422     {
1423       visitor.MarkAsComplete();
1424     }
1425 
1426     LOG(INFO) << "Number of matching resources: " << countResults;
1427   }
1428 
1429 
1430 
1431   namespace
1432   {
1433     class ModalitiesInStudyVisitor : public ServerContext::ILookupVisitor
1434     {
1435     private:
1436       class Study : public boost::noncopyable
1437       {
1438       private:
1439         std::string            orthancId_;
1440         std::string            instanceId_;
1441         DicomMap               mainDicomTags_;
1442         Json::Value            dicomAsJson_;
1443         std::set<std::string>  modalitiesInStudy_;
1444 
1445       public:
Study(const std::string & instanceId,const DicomMap & seriesTags)1446         Study(const std::string& instanceId,
1447               const DicomMap& seriesTags) :
1448           instanceId_(instanceId),
1449           dicomAsJson_(Json::nullValue)
1450         {
1451           {
1452             DicomMap tmp;
1453             tmp.Assign(seriesTags);
1454             tmp.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "dummy", false);
1455             DicomInstanceHasher hasher(tmp);
1456             orthancId_ = hasher.HashStudy();
1457           }
1458 
1459           mainDicomTags_.MergeMainDicomTags(seriesTags, ResourceType_Study);
1460           mainDicomTags_.MergeMainDicomTags(seriesTags, ResourceType_Patient);
1461           AddModality(seriesTags);
1462         }
1463 
AddModality(const DicomMap & seriesTags)1464         void AddModality(const DicomMap& seriesTags)
1465         {
1466           std::string modality;
1467           if (seriesTags.LookupStringValue(modality, DICOM_TAG_MODALITY, false) &&
1468               !modality.empty())
1469           {
1470             modalitiesInStudy_.insert(modality);
1471           }
1472         }
1473 
SetDicomAsJson(const Json::Value & dicomAsJson)1474         void SetDicomAsJson(const Json::Value& dicomAsJson)
1475         {
1476           dicomAsJson_ = dicomAsJson;
1477         }
1478 
GetOrthancId() const1479         const std::string& GetOrthancId() const
1480         {
1481           return orthancId_;
1482         }
1483 
GetInstanceId() const1484         const std::string& GetInstanceId() const
1485         {
1486           return instanceId_;
1487         }
1488 
GetMainDicomTags() const1489         const DicomMap& GetMainDicomTags() const
1490         {
1491           return mainDicomTags_;
1492         }
1493 
GetDicomAsJson() const1494         const Json::Value* GetDicomAsJson() const
1495         {
1496           if (dicomAsJson_.type() == Json::nullValue)
1497           {
1498             return NULL;
1499           }
1500           else
1501           {
1502             return &dicomAsJson_;
1503           }
1504         }
1505       };
1506 
1507       typedef std::map<std::string, Study*>  Studies;
1508 
1509       bool     isDicomAsJsonNeeded_;
1510       bool     complete_;
1511       Studies  studies_;
1512 
1513     public:
ModalitiesInStudyVisitor(bool isDicomAsJsonNeeded)1514       explicit ModalitiesInStudyVisitor(bool isDicomAsJsonNeeded) :
1515         isDicomAsJsonNeeded_(isDicomAsJsonNeeded),
1516         complete_(false)
1517       {
1518       }
1519 
~ModalitiesInStudyVisitor()1520       ~ModalitiesInStudyVisitor()
1521       {
1522         for (Studies::const_iterator it = studies_.begin(); it != studies_.end(); ++it)
1523         {
1524           assert(it->second != NULL);
1525           delete it->second;
1526         }
1527 
1528         studies_.clear();
1529       }
1530 
IsDicomAsJsonNeeded() const1531       virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
1532       {
1533         return isDicomAsJsonNeeded_;
1534       }
1535 
MarkAsComplete()1536       virtual void MarkAsComplete() ORTHANC_OVERRIDE
1537       {
1538         complete_ = true;
1539       }
1540 
Visit(const std::string & publicId,const std::string & instanceId,const DicomMap & seriesTags,const Json::Value * dicomAsJson)1541       virtual void Visit(const std::string& publicId,
1542                          const std::string& instanceId,
1543                          const DicomMap& seriesTags,
1544                          const Json::Value* dicomAsJson) ORTHANC_OVERRIDE
1545       {
1546         std::string studyInstanceUid;
1547         if (seriesTags.LookupStringValue(studyInstanceUid, DICOM_TAG_STUDY_INSTANCE_UID, false))
1548         {
1549           Studies::iterator found = studies_.find(studyInstanceUid);
1550           if (found == studies_.end())
1551           {
1552             // New study
1553             std::unique_ptr<Study> study(new Study(instanceId, seriesTags));
1554 
1555             if (dicomAsJson != NULL)
1556             {
1557               study->SetDicomAsJson(*dicomAsJson);
1558             }
1559 
1560             studies_[studyInstanceUid] = study.release();
1561           }
1562           else
1563           {
1564             // Already existing study
1565             found->second->AddModality(seriesTags);
1566           }
1567         }
1568       }
1569 
Forward(ILookupVisitor & callerVisitor,size_t since,size_t limit) const1570       void Forward(ILookupVisitor& callerVisitor,
1571                    size_t since,
1572                    size_t limit) const
1573       {
1574         size_t index = 0;
1575         size_t countForwarded = 0;
1576 
1577         for (Studies::const_iterator it = studies_.begin(); it != studies_.end(); ++it, index++)
1578         {
1579           if (limit == 0 ||
1580               (index >= since &&
1581                index < limit))
1582           {
1583             assert(it->second != NULL);
1584             const Study& study = *it->second;
1585 
1586             countForwarded++;
1587             callerVisitor.Visit(study.GetOrthancId(), study.GetInstanceId(),
1588                                 study.GetMainDicomTags(), study.GetDicomAsJson());
1589           }
1590         }
1591 
1592         if (countForwarded == studies_.size())
1593         {
1594           callerVisitor.MarkAsComplete();
1595         }
1596       }
1597     };
1598   }
1599 
1600 
Apply(ILookupVisitor & visitor,const DatabaseLookup & lookup,ResourceType queryLevel,size_t since,size_t limit)1601   void ServerContext::Apply(ILookupVisitor& visitor,
1602                             const DatabaseLookup& lookup,
1603                             ResourceType queryLevel,
1604                             size_t since,
1605                             size_t limit)
1606   {
1607     if (queryLevel == ResourceType_Study &&
1608         lookup.HasTag(DICOM_TAG_MODALITIES_IN_STUDY))
1609     {
1610       // Convert the study-level query, into a series-level query,
1611       // where "ModalitiesInStudy" is replaced by "Modality"
1612       DatabaseLookup seriesLookup;
1613 
1614       for (size_t i = 0; i < lookup.GetConstraintsCount(); i++)
1615       {
1616         const DicomTagConstraint& constraint = lookup.GetConstraint(i);
1617         if (constraint.GetTag() == DICOM_TAG_MODALITIES_IN_STUDY)
1618         {
1619           if ((constraint.GetConstraintType() == ConstraintType_Equal && constraint.GetValue().empty()) ||
1620               (constraint.GetConstraintType() == ConstraintType_List && constraint.GetValues().empty()))
1621           {
1622             // Ignore universal lookup on "ModalitiesInStudy" (0008,0061),
1623             // this should have been handled by the caller
1624             ApplyInternal(visitor, lookup, queryLevel, since, limit);
1625             return;
1626           }
1627           else
1628           {
1629             DicomTagConstraint modality(constraint);
1630             modality.SetTag(DICOM_TAG_MODALITY);
1631             seriesLookup.AddConstraint(modality);
1632           }
1633         }
1634         else
1635         {
1636           seriesLookup.AddConstraint(constraint);
1637         }
1638       }
1639 
1640       ModalitiesInStudyVisitor seriesVisitor(visitor.IsDicomAsJsonNeeded());
1641       ApplyInternal(seriesVisitor, seriesLookup, ResourceType_Series, 0, 0);
1642       seriesVisitor.Forward(visitor, since, limit);
1643     }
1644     else
1645     {
1646       ApplyInternal(visitor, lookup, queryLevel, since, limit);
1647     }
1648   }
1649 
1650 
LookupOrReconstructMetadata(std::string & target,const std::string & publicId,ResourceType level,MetadataType metadata)1651   bool ServerContext::LookupOrReconstructMetadata(std::string& target,
1652                                                   const std::string& publicId,
1653                                                   ResourceType level,
1654                                                   MetadataType metadata)
1655   {
1656     // This is a backwards-compatibility function, that can
1657     // reconstruct metadata that were not generated by an older
1658     // release of Orthanc
1659 
1660     if (metadata == MetadataType_Instance_SopClassUid ||
1661         metadata == MetadataType_Instance_TransferSyntax)
1662     {
1663       int64_t revision;  // Ignored
1664       if (index_.LookupMetadata(target, revision, publicId, level, metadata))
1665       {
1666         return true;
1667       }
1668       else
1669       {
1670         // These metadata are mandatory in DICOM instances, and were
1671         // introduced in Orthanc 1.2.0. The fact that
1672         // "LookupMetadata()" has failed indicates that this database
1673         // comes from an older release of Orthanc.
1674 
1675         DicomTag tag(0, 0);
1676 
1677         switch (metadata)
1678         {
1679           case MetadataType_Instance_SopClassUid:
1680             tag = DICOM_TAG_SOP_CLASS_UID;
1681             break;
1682 
1683           case MetadataType_Instance_TransferSyntax:
1684             tag = DICOM_TAG_TRANSFER_SYNTAX_UID;
1685             break;
1686 
1687           default:
1688             throw OrthancException(ErrorCode_InternalError);
1689         }
1690 
1691         Json::Value dicomAsJson;
1692         ReadDicomAsJson(dicomAsJson, publicId);
1693 
1694         DicomMap tags;
1695         tags.FromDicomAsJson(dicomAsJson);
1696 
1697         const DicomValue* value = tags.TestAndGetValue(tag);
1698 
1699         if (value != NULL &&
1700             !value->IsNull() &&
1701             !value->IsBinary())
1702         {
1703           target = value->GetContent();
1704 
1705           // Store for reuse
1706           index_.OverwriteMetadata(publicId, metadata, target);
1707           return true;
1708         }
1709         else
1710         {
1711           // Should never happen
1712           return false;
1713         }
1714       }
1715     }
1716     else
1717     {
1718       // No backward
1719       int64_t revision;  // Ignored
1720       return index_.LookupMetadata(target, revision, publicId, level, metadata);
1721     }
1722   }
1723 
1724 
AddChildInstances(SetOfInstancesJob & job,const std::string & publicId)1725   void ServerContext::AddChildInstances(SetOfInstancesJob& job,
1726                                         const std::string& publicId)
1727   {
1728     std::list<std::string> instances;
1729     GetIndex().GetChildInstances(instances, publicId);
1730 
1731     job.Reserve(job.GetInstancesCount() + instances.size());
1732 
1733     for (std::list<std::string>::const_iterator
1734            it = instances.begin(); it != instances.end(); ++it)
1735     {
1736       job.AddInstance(*it);
1737     }
1738   }
1739 
1740 
SignalUpdatedModalities()1741   void ServerContext::SignalUpdatedModalities()
1742   {
1743 #if ORTHANC_ENABLE_PLUGINS == 1
1744     if (HasPlugins())
1745     {
1746       GetPlugins().SignalUpdatedModalities();
1747     }
1748 #endif
1749   }
1750 
1751 
SignalUpdatedPeers()1752   void ServerContext::SignalUpdatedPeers()
1753   {
1754 #if ORTHANC_ENABLE_PLUGINS == 1
1755     if (HasPlugins())
1756     {
1757       GetPlugins().SignalUpdatedPeers();
1758     }
1759 #endif
1760   }
1761 
1762 
1763   IStorageCommitmentFactory::ILookupHandler*
CreateStorageCommitment(const std::string & jobId,const std::string & transactionUid,const std::vector<std::string> & sopClassUids,const std::vector<std::string> & sopInstanceUids,const std::string & remoteAet,const std::string & calledAet)1764   ServerContext::CreateStorageCommitment(const std::string& jobId,
1765                                          const std::string& transactionUid,
1766                                          const std::vector<std::string>& sopClassUids,
1767                                          const std::vector<std::string>& sopInstanceUids,
1768                                          const std::string& remoteAet,
1769                                          const std::string& calledAet)
1770   {
1771 #if ORTHANC_ENABLE_PLUGINS == 1
1772     if (HasPlugins())
1773     {
1774       return GetPlugins().CreateStorageCommitment(
1775         jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet);
1776     }
1777 #endif
1778 
1779     return NULL;
1780   }
1781 
1782 
DecodeDicomFrame(const std::string & publicId,unsigned int frameIndex)1783   ImageAccessor* ServerContext::DecodeDicomFrame(const std::string& publicId,
1784                                                  unsigned int frameIndex)
1785   {
1786     if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before)
1787     {
1788       // Use Orthanc's built-in decoder, using the cache to speed-up
1789       // things on multi-frame images
1790 
1791       std::unique_ptr<ImageAccessor> decoded;
1792       try
1793       {
1794         ServerContext::DicomCacheLocker locker(*this, publicId);
1795         decoded.reset(locker.GetDicom().DecodeFrame(frameIndex));
1796       }
1797       catch (OrthancException& e)
1798       {
1799       }
1800 
1801       if (decoded.get() != NULL)
1802       {
1803         return decoded.release();
1804       }
1805     }
1806 
1807 #if ORTHANC_ENABLE_PLUGINS == 1
1808     if (HasPlugins() &&
1809         GetPlugins().HasCustomImageDecoder())
1810     {
1811       // TODO: Store the raw buffer in the DicomCacheLocker
1812       std::string dicomContent;
1813       ReadDicom(dicomContent, publicId);
1814 
1815       std::unique_ptr<ImageAccessor> decoded;
1816       try
1817       {
1818         decoded.reset(GetPlugins().Decode(dicomContent.c_str(), dicomContent.size(), frameIndex));
1819       }
1820       catch (OrthancException& e)
1821       {
1822       }
1823 
1824       if (decoded.get() != NULL)
1825       {
1826         return decoded.release();
1827       }
1828       else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
1829       {
1830         LOG(INFO) << "The installed image decoding plugins cannot handle an image, "
1831                   << "fallback to the built-in DCMTK decoder";
1832       }
1833     }
1834 #endif
1835 
1836     if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
1837     {
1838       ServerContext::DicomCacheLocker locker(*this, publicId);
1839       return locker.GetDicom().DecodeFrame(frameIndex);
1840     }
1841     else
1842     {
1843       return NULL;  // Built-in decoder is disabled
1844     }
1845   }
1846 
1847 
DecodeDicomFrame(const DicomInstanceToStore & dicom,unsigned int frameIndex)1848   ImageAccessor* ServerContext::DecodeDicomFrame(const DicomInstanceToStore& dicom,
1849                                                  unsigned int frameIndex)
1850   {
1851     if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before)
1852     {
1853       std::unique_ptr<ImageAccessor> decoded;
1854       try
1855       {
1856         decoded.reset(dicom.DecodeFrame(frameIndex));
1857       }
1858       catch (OrthancException& e)
1859       {
1860       }
1861 
1862       if (decoded.get() != NULL)
1863       {
1864         return decoded.release();
1865       }
1866     }
1867 
1868 #if ORTHANC_ENABLE_PLUGINS == 1
1869     if (HasPlugins() &&
1870         GetPlugins().HasCustomImageDecoder())
1871     {
1872       std::unique_ptr<ImageAccessor> decoded;
1873       try
1874       {
1875         decoded.reset(GetPlugins().Decode(dicom.GetBufferData(), dicom.GetBufferSize(), frameIndex));
1876       }
1877       catch (OrthancException& e)
1878       {
1879       }
1880 
1881       if (decoded.get() != NULL)
1882       {
1883         return decoded.release();
1884       }
1885       else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
1886       {
1887         LOG(INFO) << "The installed image decoding plugins cannot handle an image, "
1888                   << "fallback to the built-in DCMTK decoder";
1889       }
1890     }
1891 #endif
1892 
1893     if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
1894     {
1895       return dicom.DecodeFrame(frameIndex);
1896     }
1897     else
1898     {
1899       return NULL;
1900     }
1901   }
1902 
1903 
DecodeDicomFrame(const void * dicom,size_t size,unsigned int frameIndex)1904   ImageAccessor* ServerContext::DecodeDicomFrame(const void* dicom,
1905                                                  size_t size,
1906                                                  unsigned int frameIndex)
1907   {
1908     std::unique_ptr<DicomInstanceToStore> instance(DicomInstanceToStore::CreateFromBuffer(dicom, size));
1909     return DecodeDicomFrame(*instance, frameIndex);
1910   }
1911 
1912 
StoreWithTranscoding(std::string & sopClassUid,std::string & sopInstanceUid,DicomStoreUserConnection & connection,const std::string & dicom,bool hasMoveOriginator,const std::string & moveOriginatorAet,uint16_t moveOriginatorId)1913   void ServerContext::StoreWithTranscoding(std::string& sopClassUid,
1914                                            std::string& sopInstanceUid,
1915                                            DicomStoreUserConnection& connection,
1916                                            const std::string& dicom,
1917                                            bool hasMoveOriginator,
1918                                            const std::string& moveOriginatorAet,
1919                                            uint16_t moveOriginatorId)
1920   {
1921     const void* data = dicom.empty() ? NULL : dicom.c_str();
1922 
1923     if (!transcodeDicomProtocol_ ||
1924         !connection.GetParameters().GetRemoteModality().IsTranscodingAllowed())
1925     {
1926       connection.Store(sopClassUid, sopInstanceUid, data, dicom.size(),
1927                        hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
1928     }
1929     else
1930     {
1931       connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(), preferredTransferSyntax_,
1932                            hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
1933     }
1934   }
1935 
1936 
Transcode(DicomImage & target,DicomImage & source,const std::set<DicomTransferSyntax> & allowedSyntaxes,bool allowNewSopInstanceUid)1937   bool ServerContext::Transcode(DicomImage& target,
1938                                 DicomImage& source /* in, "GetParsed()" possibly modified */,
1939                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
1940                                 bool allowNewSopInstanceUid)
1941   {
1942     if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before)
1943     {
1944       if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid))
1945       {
1946         return true;
1947       }
1948     }
1949 
1950 #if ORTHANC_ENABLE_PLUGINS == 1
1951     if (HasPlugins() &&
1952         GetPlugins().HasCustomTranscoder())
1953     {
1954       if (GetPlugins().Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid))
1955       {
1956         return true;
1957       }
1958       else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
1959       {
1960         LOG(INFO) << "The installed transcoding plugins cannot handle an image, "
1961                   << "fallback to the built-in DCMTK transcoder";
1962       }
1963     }
1964 #endif
1965 
1966     if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
1967     {
1968       return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid);
1969     }
1970     else
1971     {
1972       return false;
1973     }
1974   }
1975 
GetDeidentifiedContent(const DicomElement & element) const1976   const std::string& ServerContext::GetDeidentifiedContent(const DicomElement &element) const
1977   {
1978     static const std::string redactedContent = "*** POTENTIAL PHI ***";
1979 
1980     const DicomTag& tag = element.GetTag();
1981     if (deidentifyLogs_ && (
1982           logsDeidentifierRules_.IsCleared(tag) ||
1983           logsDeidentifierRules_.IsRemoved(tag) ||
1984           logsDeidentifierRules_.IsReplaced(tag)))
1985     {
1986       return redactedContent;
1987     }
1988     else
1989     {
1990       return element.GetValue().GetContent();
1991     }
1992   }
1993 
1994 
GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax> & syntaxes)1995   void ServerContext::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes)
1996   {
1997     boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
1998     syntaxes = acceptedTransferSyntaxes_;
1999   }
2000 
2001 
SetAcceptedTransferSyntaxes(const std::set<DicomTransferSyntax> & syntaxes)2002   void ServerContext::SetAcceptedTransferSyntaxes(const std::set<DicomTransferSyntax>& syntaxes)
2003   {
2004     boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
2005     acceptedTransferSyntaxes_ = syntaxes;
2006   }
2007 
2008 
IsUnknownSopClassAccepted()2009   bool ServerContext::IsUnknownSopClassAccepted()
2010   {
2011     boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
2012     return isUnknownSopClassAccepted_;
2013   }
2014 
2015 
SetUnknownSopClassAccepted(bool accepted)2016   void ServerContext::SetUnknownSopClassAccepted(bool accepted)
2017   {
2018     boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
2019     isUnknownSopClassAccepted_ = accepted;
2020   }
2021 }
2022