comparison OrthancStone/Sources/Oracle/GenericOracleRunner.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Framework/Oracle/GenericOracleRunner.cpp@121d01aa328e
children 85e117739eca
comparison
equal deleted inserted replaced
1511:9dfeee74c1e6 1512:244ad1e4e76a
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 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 "GenericOracleRunner.h"
23
24 #if !defined(ORTHANC_ENABLE_DCMTK)
25 # error The macro ORTHANC_ENABLE_DCMTK must be defined
26 #endif
27
28 #include "GetOrthancImageCommand.h"
29 #include "GetOrthancWebViewerJpegCommand.h"
30 #include "HttpCommand.h"
31 #include "OracleCommandExceptionMessage.h"
32 #include "OrthancRestApiCommand.h"
33 #include "ParseDicomFromFileCommand.h"
34 #include "ParseDicomFromWadoCommand.h"
35 #include "ReadFileCommand.h"
36
37 #if ORTHANC_ENABLE_DCMTK == 1
38 # include "ParseDicomSuccessMessage.h"
39 # include <dcmtk/dcmdata/dcdeftag.h>
40 # include <dcmtk/dcmdata/dcfilefo.h>
41 static unsigned int BUCKET_DICOMDIR = 0;
42 static unsigned int BUCKET_SOP = 1;
43 #endif
44
45 #include <Compression/GzipCompressor.h>
46 #include <HttpClient.h>
47 #include <OrthancException.h>
48 #include <Toolbox.h>
49 #include <SystemToolbox.h>
50
51 #include <boost/filesystem.hpp>
52
53
54
55 namespace OrthancStone
56 {
57 static void CopyHttpHeaders(Orthanc::HttpClient& client,
58 const Orthanc::HttpClient::HttpHeaders& headers)
59 {
60 for (Orthanc::HttpClient::HttpHeaders::const_iterator
61 it = headers.begin(); it != headers.end(); it++ )
62 {
63 client.AddHeader(it->first, it->second);
64 }
65 }
66
67
68 static void DecodeAnswer(std::string& answer,
69 const Orthanc::HttpClient::HttpHeaders& headers)
70 {
71 Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
72
73 for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin();
74 it != headers.end(); ++it)
75 {
76 std::string s;
77 Orthanc::Toolbox::ToLowerCase(s, it->first);
78
79 if (s == "content-encoding")
80 {
81 if (it->second == "gzip")
82 {
83 contentEncoding = Orthanc::HttpCompression_Gzip;
84 }
85 else
86 {
87 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
88 "Unsupported HTTP Content-Encoding: " + it->second);
89 }
90
91 break;
92 }
93 }
94
95 if (contentEncoding == Orthanc::HttpCompression_Gzip)
96 {
97 std::string compressed;
98 answer.swap(compressed);
99
100 Orthanc::GzipCompressor compressor;
101 compressor.Uncompress(answer, compressed.c_str(), compressed.size());
102
103 LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size()
104 << " to " << answer.size() << " bytes";
105 }
106 }
107
108
109 static void RunHttpCommand(std::string& answer,
110 Orthanc::HttpClient::HttpHeaders& answerHeaders,
111 const HttpCommand& command)
112 {
113 Orthanc::HttpClient client;
114 client.SetUrl(command.GetUrl());
115 client.SetMethod(command.GetMethod());
116 client.SetTimeout(command.GetTimeout());
117
118 CopyHttpHeaders(client, command.GetHttpHeaders());
119
120 if (command.HasCredentials())
121 {
122 client.SetCredentials(command.GetUsername().c_str(), command.GetPassword().c_str());
123 }
124
125 if (command.GetMethod() == Orthanc::HttpMethod_Post ||
126 command.GetMethod() == Orthanc::HttpMethod_Put)
127 {
128 client.SetBody(command.GetBody());
129 }
130
131 client.ApplyAndThrowException(answer, answerHeaders);
132 DecodeAnswer(answer, answerHeaders);
133 }
134
135
136 static void RunInternal(boost::weak_ptr<IObserver> receiver,
137 IMessageEmitter& emitter,
138 const HttpCommand& command)
139 {
140 std::string answer;
141 Orthanc::HttpClient::HttpHeaders answerHeaders;
142 RunHttpCommand(answer, answerHeaders, command);
143
144 HttpCommand::SuccessMessage message(command, answerHeaders, answer);
145 emitter.EmitMessage(receiver, message);
146 }
147
148
149 static void RunOrthancRestApiCommand(std::string& answer,
150 Orthanc::HttpClient::HttpHeaders& answerHeaders,
151 const Orthanc::WebServiceParameters& orthanc,
152 const OrthancRestApiCommand& command)
153 {
154 Orthanc::HttpClient client(orthanc, command.GetUri());
155 client.SetRedirectionFollowed(false);
156 client.SetMethod(command.GetMethod());
157 client.SetTimeout(command.GetTimeout());
158
159 CopyHttpHeaders(client, command.GetHttpHeaders());
160
161 if (command.GetMethod() == Orthanc::HttpMethod_Post ||
162 command.GetMethod() == Orthanc::HttpMethod_Put)
163 {
164 client.SetBody(command.GetBody());
165 }
166
167 client.ApplyAndThrowException(answer, answerHeaders);
168 DecodeAnswer(answer, answerHeaders);
169 }
170
171
172 static void RunInternal(boost::weak_ptr<IObserver> receiver,
173 IMessageEmitter& emitter,
174 const Orthanc::WebServiceParameters& orthanc,
175 const OrthancRestApiCommand& command)
176 {
177 std::string answer;
178 Orthanc::HttpClient::HttpHeaders answerHeaders;
179 RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command);
180
181 OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
182 emitter.EmitMessage(receiver, message);
183 }
184
185
186 static void RunInternal(boost::weak_ptr<IObserver> receiver,
187 IMessageEmitter& emitter,
188 const Orthanc::WebServiceParameters& orthanc,
189 const GetOrthancImageCommand& command)
190 {
191 Orthanc::HttpClient client(orthanc, command.GetUri());
192 client.SetRedirectionFollowed(false);
193 client.SetTimeout(command.GetTimeout());
194
195 CopyHttpHeaders(client, command.GetHttpHeaders());
196
197 std::string answer;
198 Orthanc::HttpClient::HttpHeaders answerHeaders;
199 client.ApplyAndThrowException(answer, answerHeaders);
200
201 DecodeAnswer(answer, answerHeaders);
202
203 command.ProcessHttpAnswer(receiver, emitter, answer, answerHeaders);
204 }
205
206
207 static void RunInternal(boost::weak_ptr<IObserver> receiver,
208 IMessageEmitter& emitter,
209 const Orthanc::WebServiceParameters& orthanc,
210 const GetOrthancWebViewerJpegCommand& command)
211 {
212 Orthanc::HttpClient client(orthanc, command.GetUri());
213 client.SetRedirectionFollowed(false);
214 client.SetTimeout(command.GetTimeout());
215
216 CopyHttpHeaders(client, command.GetHttpHeaders());
217
218 std::string answer;
219 Orthanc::HttpClient::HttpHeaders answerHeaders;
220 client.ApplyAndThrowException(answer, answerHeaders);
221
222 DecodeAnswer(answer, answerHeaders);
223
224 command.ProcessHttpAnswer(receiver, emitter, answer);
225 }
226
227
228 static std::string GetPath(const std::string& root,
229 const std::string& file)
230 {
231 boost::filesystem::path a(root);
232 boost::filesystem::path b(file);
233
234 boost::filesystem::path c;
235 if (b.is_absolute())
236 {
237 c = b;
238 }
239 else
240 {
241 c = a / b;
242 }
243
244 return c.string();
245 }
246
247
248 static void RunInternal(boost::weak_ptr<IObserver> receiver,
249 IMessageEmitter& emitter,
250 const std::string& root,
251 const ReadFileCommand& command)
252 {
253 std::string path = GetPath(root, command.GetPath());
254 LOG(TRACE) << "Oracle reading file: " << path;
255
256 std::string content;
257 Orthanc::SystemToolbox::ReadFile(content, path, true /* log */);
258
259 ReadFileCommand::SuccessMessage message(command, content);
260 emitter.EmitMessage(receiver, message);
261 }
262
263
264 #if ORTHANC_ENABLE_DCMTK == 1
265 static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize, /* OUT */
266 const std::string& path,
267 bool isPixelData)
268 {
269 if (!Orthanc::SystemToolbox::IsRegularFile(path))
270 {
271 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
272 }
273
274 LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without")
275 << " pixel data: " << path;
276
277 boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
278
279 fileSize = Orthanc::SystemToolbox::GetFileSize(path);
280
281 // Check for 32bit systems
282 if (fileSize != static_cast<uint64_t>(static_cast<size_t>(fileSize)))
283 {
284 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
285 }
286
287 DcmFileFormat dicom;
288 bool ok;
289
290 if (isPixelData)
291 {
292 ok = dicom.loadFile(path.c_str()).good();
293 }
294 else
295 {
296 #if DCMTK_VERSION_NUMBER >= 362
297 /**
298 * NB : We could stop at (0x3007, 0x0000) instead of
299 * DCM_PixelData as the Stone framework does not use further
300 * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still
301 * use "PixelData" as this does not change the runtime much, and
302 * as it is more explicit.
303 **/
304 static const DcmTagKey STOP = DCM_PixelData;
305 //static const DcmTagKey STOP(0x3007, 0x0000);
306
307 ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange,
308 DCM_MaxReadLength, ERM_autoDetect, STOP).good();
309 #else
310 // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2
311 ok = dicom.loadFile(path.c_str()).good();
312 #endif
313 }
314
315 if (ok)
316 {
317 std::unique_ptr<Orthanc::ParsedDicomFile> result(new Orthanc::ParsedDicomFile(dicom));
318
319 boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
320 LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms";
321
322 return result.release();
323 }
324 else
325 {
326 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
327 "Cannot parse file: " + path);
328 }
329 }
330
331
332 static void RunInternal(boost::weak_ptr<IObserver> receiver,
333 IMessageEmitter& emitter,
334 boost::shared_ptr<ParsedDicomCache> cache,
335 const std::string& root,
336 const ParseDicomFromFileCommand& command)
337 {
338 const std::string path = GetPath(root, command.GetPath());
339
340 if (cache)
341 {
342 ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path);
343 if (reader.IsValid() &&
344 (!command.IsPixelDataIncluded() ||
345 reader.HasPixelData()))
346 {
347 // Reuse the DICOM file from the cache
348 ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
349 reader.GetFileSize(), reader.HasPixelData());
350 emitter.EmitMessage(receiver, message);
351 return;
352 }
353 }
354
355 uint64_t fileSize;
356 std::unique_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded()));
357
358 if (fileSize != static_cast<size_t>(fileSize))
359 {
360 // Cannot load such a large file on 32-bit architecture
361 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
362 }
363
364 {
365 ParseDicomSuccessMessage message
366 (command, command.GetSource(), *parsed,
367 static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
368 emitter.EmitMessage(receiver, message);
369 }
370
371 if (cache)
372 {
373 // Store it into the cache for future use
374
375 // Invalidate to overwrite DICOM instance that would already
376 // be stored without pixel data
377 cache->Invalidate(BUCKET_DICOMDIR, path);
378
379 cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(),
380 static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
381 }
382 }
383
384
385 static void RunInternal(boost::weak_ptr<IObserver> receiver,
386 IMessageEmitter& emitter,
387 boost::shared_ptr<ParsedDicomCache> cache,
388 const Orthanc::WebServiceParameters& orthanc,
389 const ParseDicomFromWadoCommand& command)
390 {
391 if (cache)
392 {
393 ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid());
394 if (reader.IsValid() &&
395 reader.HasPixelData())
396 {
397 // Reuse the DICOM file from the cache
398 ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
399 reader.GetFileSize(), reader.HasPixelData());
400 emitter.EmitMessage(receiver, message);
401 return;
402 }
403 }
404
405 std::string answer;
406 Orthanc::HttpClient::HttpHeaders answerHeaders;
407
408 switch (command.GetRestCommand().GetType())
409 {
410 case IOracleCommand::Type_Http:
411 RunHttpCommand(answer, answerHeaders, dynamic_cast<const HttpCommand&>(command.GetRestCommand()));
412 break;
413
414 case IOracleCommand::Type_OrthancRestApi:
415 RunOrthancRestApiCommand(answer, answerHeaders, orthanc,
416 dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand()));
417 break;
418
419 default:
420 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
421 }
422
423 size_t fileSize;
424 std::unique_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
425
426 {
427 ParseDicomSuccessMessage message(command, command.GetSource(), *parsed, fileSize,
428 true /* pixel data always is included in WADO-RS */);
429 emitter.EmitMessage(receiver, message);
430 }
431
432 if (cache)
433 {
434 // Store it into the cache for future use
435 cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true);
436 }
437 }
438 #endif
439
440
441 void GenericOracleRunner::Run(boost::weak_ptr<IObserver> receiver,
442 IMessageEmitter& emitter,
443 const IOracleCommand& command)
444 {
445 Orthanc::ErrorCode error = Orthanc::ErrorCode_Success;
446
447 try
448 {
449 switch (command.GetType())
450 {
451 case IOracleCommand::Type_Sleep:
452 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
453 "Sleep command cannot be executed by the runner");
454
455 case IOracleCommand::Type_Http:
456 RunInternal(receiver, emitter, dynamic_cast<const HttpCommand&>(command));
457 break;
458
459 case IOracleCommand::Type_OrthancRestApi:
460 RunInternal(receiver, emitter, orthanc_,
461 dynamic_cast<const OrthancRestApiCommand&>(command));
462 break;
463
464 case IOracleCommand::Type_GetOrthancImage:
465 RunInternal(receiver, emitter, orthanc_,
466 dynamic_cast<const GetOrthancImageCommand&>(command));
467 break;
468
469 case IOracleCommand::Type_GetOrthancWebViewerJpeg:
470 RunInternal(receiver, emitter, orthanc_,
471 dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command));
472 break;
473
474 case IOracleCommand::Type_ReadFile:
475 RunInternal(receiver, emitter, rootDirectory_,
476 dynamic_cast<const ReadFileCommand&>(command));
477 break;
478
479 case IOracleCommand::Type_ParseDicomFromFile:
480 case IOracleCommand::Type_ParseDicomFromWado:
481 #if ORTHANC_ENABLE_DCMTK == 1
482 switch (command.GetType())
483 {
484 case IOracleCommand::Type_ParseDicomFromFile:
485 RunInternal(receiver, emitter, dicomCache_, rootDirectory_,
486 dynamic_cast<const ParseDicomFromFileCommand&>(command));
487 break;
488
489 case IOracleCommand::Type_ParseDicomFromWado:
490 RunInternal(receiver, emitter, dicomCache_, orthanc_,
491 dynamic_cast<const ParseDicomFromWadoCommand&>(command));
492 break;
493
494 default:
495 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
496 }
497 break;
498 #else
499 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
500 "DCMTK must be enabled to parse DICOM files");
501 #endif
502
503 default:
504 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
505 }
506 }
507 catch (Orthanc::OrthancException& e)
508 {
509 LOG(ERROR) << "Exception within the oracle: " << e.What();
510 error = e.GetErrorCode();
511 }
512 catch (...)
513 {
514 LOG(ERROR) << "Threaded exception within the oracle";
515 error = Orthanc::ErrorCode_InternalError;
516 }
517
518 if (error != Orthanc::ErrorCode_Success)
519 {
520 OracleCommandExceptionMessage message(command, error);
521 emitter.EmitMessage(receiver, message);
522 }
523 }
524 }