comparison Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp @ 0:39585ba26f20

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 15 Jun 2023 09:48:46 +0200
parents
children 1bda8ea224de
comparison
equal deleted inserted replaced
-1:000000000000 0:39585ba26f20
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-2023 Osimis S.A., Belgium
6 * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 *
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation, either version 3 of the
11 * License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 **/
21
22
23 #include "OrthancPluginCppWrapper.h"
24
25 #include <boost/algorithm/string/predicate.hpp>
26 #include <boost/move/unique_ptr.hpp>
27 #include <boost/thread.hpp>
28
29
30 #include <json/reader.h>
31 #include <json/version.h>
32 #include <json/writer.h>
33
34 #if !defined(JSONCPP_VERSION_MAJOR) || !defined(JSONCPP_VERSION_MINOR)
35 # error Cannot access the version of JsonCpp
36 #endif
37
38
39 /**
40 * We use deprecated "Json::Reader", "Json::StyledWriter" and
41 * "Json::FastWriter" if JsonCpp < 1.7.0. This choice is rather
42 * arbitrary, but if Json >= 1.9.0, gcc generates explicit deprecation
43 * warnings (clang was warning in earlier versions). For reference,
44 * these classes seem to have been deprecated since JsonCpp 1.4.0 (on
45 * February 2015) by the following changeset:
46 * https://github.com/open-source-parsers/jsoncpp/commit/8df98f6112890d6272734975dd6d70cf8999bb22
47 **/
48 #if (JSONCPP_VERSION_MAJOR >= 2 || \
49 (JSONCPP_VERSION_MAJOR == 1 && JSONCPP_VERSION_MINOR >= 8))
50 # define JSONCPP_USE_DEPRECATED 0
51 #else
52 # define JSONCPP_USE_DEPRECATED 1
53 #endif
54
55
56 #if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
57 static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
58 #endif
59
60
61 namespace OrthancPlugins
62 {
63 static OrthancPluginContext* globalContext_ = NULL;
64
65
66 void SetGlobalContext(OrthancPluginContext* context)
67 {
68 if (context == NULL)
69 {
70 ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
71 }
72 else if (globalContext_ == NULL)
73 {
74 globalContext_ = context;
75 }
76 else
77 {
78 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
79 }
80 }
81
82
83 bool HasGlobalContext()
84 {
85 return globalContext_ != NULL;
86 }
87
88
89 OrthancPluginContext* GetGlobalContext()
90 {
91 if (globalContext_ == NULL)
92 {
93 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
94 }
95 else
96 {
97 return globalContext_;
98 }
99 }
100
101
102 void MemoryBuffer::Check(OrthancPluginErrorCode code)
103 {
104 if (code != OrthancPluginErrorCode_Success)
105 {
106 // Prevent using garbage information
107 buffer_.data = NULL;
108 buffer_.size = 0;
109 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
110 }
111 }
112
113
114 bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
115 {
116 if (code != OrthancPluginErrorCode_Success)
117 {
118 // Prevent using garbage information
119 buffer_.data = NULL;
120 buffer_.size = 0;
121 }
122
123 if (code == OrthancPluginErrorCode_Success)
124 {
125 return true;
126 }
127 else if (code == OrthancPluginErrorCode_UnknownResource ||
128 code == OrthancPluginErrorCode_InexistentItem)
129 {
130 return false;
131 }
132 else
133 {
134 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
135 }
136 }
137
138
139 MemoryBuffer::MemoryBuffer()
140 {
141 buffer_.data = NULL;
142 buffer_.size = 0;
143 }
144
145
146 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
147 MemoryBuffer::MemoryBuffer(const void* buffer,
148 size_t size)
149 {
150 uint32_t s = static_cast<uint32_t>(size);
151 if (static_cast<size_t>(s) != size)
152 {
153 ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
154 }
155 else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
156 OrthancPluginErrorCode_Success)
157 {
158 ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
159 }
160 else
161 {
162 memcpy(buffer_.data, buffer, size);
163 }
164 }
165 #endif
166
167
168 void MemoryBuffer::Clear()
169 {
170 if (buffer_.data != NULL)
171 {
172 OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_);
173 buffer_.data = NULL;
174 buffer_.size = 0;
175 }
176 }
177
178
179 void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
180 {
181 Clear();
182
183 buffer_.data = other.data;
184 buffer_.size = other.size;
185
186 other.data = NULL;
187 other.size = 0;
188 }
189
190
191 void MemoryBuffer::Swap(MemoryBuffer& other)
192 {
193 std::swap(buffer_.data, other.buffer_.data);
194 std::swap(buffer_.size, other.buffer_.size);
195 }
196
197
198 OrthancPluginMemoryBuffer MemoryBuffer::Release()
199 {
200 OrthancPluginMemoryBuffer result = buffer_;
201
202 buffer_.data = NULL;
203 buffer_.size = 0;
204
205 return result;
206 }
207
208
209 void MemoryBuffer::ToString(std::string& target) const
210 {
211 if (buffer_.size == 0)
212 {
213 target.clear();
214 }
215 else
216 {
217 target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
218 }
219 }
220
221
222 void MemoryBuffer::ToJson(Json::Value& target) const
223 {
224 if (buffer_.data == NULL ||
225 buffer_.size == 0)
226 {
227 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
228 }
229
230 if (!ReadJson(target, buffer_.data, buffer_.size))
231 {
232 LogError("Cannot convert some memory buffer to JSON");
233 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
234 }
235 }
236
237
238 bool MemoryBuffer::RestApiGet(const std::string& uri,
239 bool applyPlugins)
240 {
241 Clear();
242
243 if (applyPlugins)
244 {
245 return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
246 }
247 else
248 {
249 return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
250 }
251 }
252
253 // helper class to convert std::map of headers to the plugin SDK C structure
254 class PluginHttpHeaders
255 {
256 private:
257 std::vector<const char*> headersKeys_;
258 std::vector<const char*> headersValues_;
259
260 public:
261 explicit PluginHttpHeaders(const std::map<std::string, std::string>& httpHeaders)
262 {
263 for (std::map<std::string, std::string>::const_iterator
264 it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
265 {
266 headersKeys_.push_back(it->first.c_str());
267 headersValues_.push_back(it->second.c_str());
268 }
269 }
270
271 const char* const* GetKeys()
272 {
273 return (headersKeys_.empty() ? NULL : &headersKeys_[0]);
274 }
275
276 const char* const* GetValues()
277 {
278 return (headersValues_.empty() ? NULL : &headersValues_[0]);
279 }
280
281 uint32_t GetSize()
282 {
283 return static_cast<uint32_t>(headersKeys_.size());
284 }
285 };
286
287 bool MemoryBuffer::RestApiGet(const std::string& uri,
288 const std::map<std::string, std::string>& httpHeaders,
289 bool applyPlugins)
290 {
291 Clear();
292
293 PluginHttpHeaders headers(httpHeaders);
294
295 return CheckHttp(OrthancPluginRestApiGet2(
296 GetGlobalContext(), &buffer_, uri.c_str(),
297 headers.GetSize(),
298 headers.GetKeys(),
299 headers.GetValues(), applyPlugins));
300 }
301
302 bool MemoryBuffer::RestApiPost(const std::string& uri,
303 const void* body,
304 size_t bodySize,
305 bool applyPlugins)
306 {
307 Clear();
308
309 // Cast for compatibility with Orthanc SDK <= 1.5.6
310 const char* b = reinterpret_cast<const char*>(body);
311
312 if (applyPlugins)
313 {
314 return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
315 }
316 else
317 {
318 return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
319 }
320 }
321
322 #if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
323
324 bool MemoryBuffer::RestApiPost(const std::string& uri,
325 const void* body,
326 size_t bodySize,
327 const std::map<std::string, std::string>& httpHeaders,
328 bool applyPlugins)
329 {
330 MemoryBuffer answerHeaders;
331 uint16_t httpStatus;
332
333 PluginHttpHeaders headers(httpHeaders);
334
335 return CheckHttp(OrthancPluginCallRestApi(GetGlobalContext(),
336 &buffer_,
337 *answerHeaders,
338 &httpStatus,
339 OrthancPluginHttpMethod_Post,
340 uri.c_str(),
341 headers.GetSize(), headers.GetKeys(), headers.GetValues(),
342 body, bodySize,
343 applyPlugins));
344 }
345
346
347 bool MemoryBuffer::RestApiPost(const std::string& uri,
348 const Json::Value& body,
349 const std::map<std::string, std::string>& httpHeaders,
350 bool applyPlugins)
351 {
352 std::string s;
353 WriteFastJson(s, body);
354 return RestApiPost(uri, s.c_str(), s.size(), httpHeaders, applyPlugins);
355 }
356 #endif
357
358 bool MemoryBuffer::RestApiPut(const std::string& uri,
359 const void* body,
360 size_t bodySize,
361 bool applyPlugins)
362 {
363 Clear();
364
365 // Cast for compatibility with Orthanc SDK <= 1.5.6
366 const char* b = reinterpret_cast<const char*>(body);
367
368 if (applyPlugins)
369 {
370 return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
371 }
372 else
373 {
374 return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
375 }
376 }
377
378
379 static bool ReadJsonInternal(Json::Value& target,
380 const void* buffer,
381 size_t size,
382 bool collectComments)
383 {
384 #if JSONCPP_USE_DEPRECATED == 1
385 Json::Reader reader;
386 return reader.parse(reinterpret_cast<const char*>(buffer),
387 reinterpret_cast<const char*>(buffer) + size, target, collectComments);
388 #else
389 Json::CharReaderBuilder builder;
390 builder.settings_["collectComments"] = collectComments;
391
392 const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
393 assert(reader.get() != NULL);
394
395 JSONCPP_STRING err;
396 if (reader->parse(reinterpret_cast<const char*>(buffer),
397 reinterpret_cast<const char*>(buffer) + size, &target, &err))
398 {
399 return true;
400 }
401 else
402 {
403 LogError("Cannot parse JSON: " + std::string(err));
404 return false;
405 }
406 #endif
407 }
408
409
410 bool ReadJson(Json::Value& target,
411 const std::string& source)
412 {
413 return ReadJson(target, source.empty() ? NULL : source.c_str(), source.size());
414 }
415
416
417 bool ReadJson(Json::Value& target,
418 const void* buffer,
419 size_t size)
420 {
421 return ReadJsonInternal(target, buffer, size, true);
422 }
423
424
425 bool ReadJsonWithoutComments(Json::Value& target,
426 const std::string& source)
427 {
428 return ReadJsonWithoutComments(target, source.empty() ? NULL : source.c_str(), source.size());
429 }
430
431
432 bool ReadJsonWithoutComments(Json::Value& target,
433 const void* buffer,
434 size_t size)
435 {
436 return ReadJsonInternal(target, buffer, size, false);
437 }
438
439
440 void WriteFastJson(std::string& target,
441 const Json::Value& source)
442 {
443 #if JSONCPP_USE_DEPRECATED == 1
444 Json::FastWriter writer;
445 target = writer.write(source);
446 #else
447 Json::StreamWriterBuilder builder;
448 builder.settings_["indentation"] = "";
449 target = Json::writeString(builder, source);
450 #endif
451 }
452
453
454 void WriteStyledJson(std::string& target,
455 const Json::Value& source)
456 {
457 #if JSONCPP_USE_DEPRECATED == 1
458 Json::StyledWriter writer;
459 target = writer.write(source);
460 #else
461 Json::StreamWriterBuilder builder;
462 builder.settings_["indentation"] = " ";
463 target = Json::writeString(builder, source);
464 #endif
465 }
466
467
468 bool MemoryBuffer::RestApiPost(const std::string& uri,
469 const Json::Value& body,
470 bool applyPlugins)
471 {
472 std::string s;
473 WriteFastJson(s, body);
474 return RestApiPost(uri, s, applyPlugins);
475 }
476
477
478 bool MemoryBuffer::RestApiPut(const std::string& uri,
479 const Json::Value& body,
480 bool applyPlugins)
481 {
482 std::string s;
483 WriteFastJson(s, body);
484 return RestApiPut(uri, s, applyPlugins);
485 }
486
487
488 void MemoryBuffer::CreateDicom(const Json::Value& tags,
489 OrthancPluginCreateDicomFlags flags)
490 {
491 Clear();
492
493 std::string s;
494 WriteFastJson(s, tags);
495
496 Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
497 }
498
499 void MemoryBuffer::CreateDicom(const Json::Value& tags,
500 const OrthancImage& pixelData,
501 OrthancPluginCreateDicomFlags flags)
502 {
503 Clear();
504
505 std::string s;
506 WriteFastJson(s, tags);
507
508 Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
509 }
510
511
512 void MemoryBuffer::ReadFile(const std::string& path)
513 {
514 Clear();
515 Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
516 }
517
518
519 void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
520 {
521 Clear();
522 Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
523 }
524
525
526 void OrthancString::Assign(char* str)
527 {
528 Clear();
529
530 if (str != NULL)
531 {
532 str_ = str;
533 }
534 }
535
536
537 void OrthancString::Clear()
538 {
539 if (str_ != NULL)
540 {
541 OrthancPluginFreeString(GetGlobalContext(), str_);
542 str_ = NULL;
543 }
544 }
545
546
547 void OrthancString::ToString(std::string& target) const
548 {
549 if (str_ == NULL)
550 {
551 target.clear();
552 }
553 else
554 {
555 target.assign(str_);
556 }
557 }
558
559
560 void OrthancString::ToJson(Json::Value& target) const
561 {
562 if (str_ == NULL)
563 {
564 LogError("Cannot convert an empty memory buffer to JSON");
565 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
566 }
567
568 if (!ReadJson(target, str_))
569 {
570 LogError("Cannot convert some memory buffer to JSON");
571 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
572 }
573 }
574
575
576 void OrthancString::ToJsonWithoutComments(Json::Value& target) const
577 {
578 if (str_ == NULL)
579 {
580 LogError("Cannot convert an empty memory buffer to JSON");
581 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
582 }
583
584 if (!ReadJsonWithoutComments(target, str_))
585 {
586 LogError("Cannot convert some memory buffer to JSON");
587 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
588 }
589 }
590
591
592 void MemoryBuffer::DicomToJson(Json::Value& target,
593 OrthancPluginDicomToJsonFormat format,
594 OrthancPluginDicomToJsonFlags flags,
595 uint32_t maxStringLength)
596 {
597 OrthancString str;
598 str.Assign(OrthancPluginDicomBufferToJson
599 (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
600 str.ToJson(target);
601 }
602
603
604 bool MemoryBuffer::HttpGet(const std::string& url,
605 const std::string& username,
606 const std::string& password)
607 {
608 Clear();
609 return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
610 username.empty() ? NULL : username.c_str(),
611 password.empty() ? NULL : password.c_str()));
612 }
613
614
615 bool MemoryBuffer::HttpPost(const std::string& url,
616 const std::string& body,
617 const std::string& username,
618 const std::string& password)
619 {
620 Clear();
621
622 if (body.size() > 0xffffffffu)
623 {
624 LogError("Cannot handle body size > 4GB");
625 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
626 }
627
628 return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
629 body.c_str(), body.size(),
630 username.empty() ? NULL : username.c_str(),
631 password.empty() ? NULL : password.c_str()));
632 }
633
634
635 bool MemoryBuffer::HttpPut(const std::string& url,
636 const std::string& body,
637 const std::string& username,
638 const std::string& password)
639 {
640 Clear();
641
642 if (body.size() > 0xffffffffu)
643 {
644 LogError("Cannot handle body size > 4GB");
645 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
646 }
647
648 return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
649 body.empty() ? NULL : body.c_str(),
650 body.size(),
651 username.empty() ? NULL : username.c_str(),
652 password.empty() ? NULL : password.c_str()));
653 }
654
655
656 void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
657 {
658 Clear();
659 Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
660 }
661
662
663 bool HttpDelete(const std::string& url,
664 const std::string& username,
665 const std::string& password)
666 {
667 OrthancPluginErrorCode error = OrthancPluginHttpDelete
668 (GetGlobalContext(), url.c_str(),
669 username.empty() ? NULL : username.c_str(),
670 password.empty() ? NULL : password.c_str());
671
672 if (error == OrthancPluginErrorCode_Success)
673 {
674 return true;
675 }
676 else if (error == OrthancPluginErrorCode_UnknownResource ||
677 error == OrthancPluginErrorCode_InexistentItem)
678 {
679 return false;
680 }
681 else
682 {
683 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
684 }
685 }
686
687
688 void LogError(const std::string& message)
689 {
690 if (HasGlobalContext())
691 {
692 OrthancPluginLogError(GetGlobalContext(), message.c_str());
693 }
694 }
695
696
697 void LogWarning(const std::string& message)
698 {
699 if (HasGlobalContext())
700 {
701 OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
702 }
703 }
704
705
706 void LogInfo(const std::string& message)
707 {
708 if (HasGlobalContext())
709 {
710 OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
711 }
712 }
713
714
715 void OrthancConfiguration::LoadConfiguration()
716 {
717 OrthancString str;
718 str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
719
720 if (str.GetContent() == NULL)
721 {
722 LogError("Cannot access the Orthanc configuration");
723 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
724 }
725
726 str.ToJsonWithoutComments(configuration_);
727
728 if (configuration_.type() != Json::objectValue)
729 {
730 LogError("Unable to read the Orthanc configuration");
731 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
732 }
733 }
734
735
736 OrthancConfiguration::OrthancConfiguration()
737 {
738 LoadConfiguration();
739 }
740
741
742 OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
743 {
744 if (loadConfiguration)
745 {
746 LoadConfiguration();
747 }
748 else
749 {
750 configuration_ = Json::objectValue;
751 }
752 }
753
754 OrthancConfiguration::OrthancConfiguration(const Json::Value& configuration, const std::string& path) :
755 configuration_(configuration),
756 path_(path)
757 {
758 }
759
760
761 std::string OrthancConfiguration::GetPath(const std::string& key) const
762 {
763 if (path_.empty())
764 {
765 return key;
766 }
767 else
768 {
769 return path_ + "." + key;
770 }
771 }
772
773
774 bool OrthancConfiguration::IsSection(const std::string& key) const
775 {
776 assert(configuration_.type() == Json::objectValue);
777
778 return (configuration_.isMember(key) &&
779 configuration_[key].type() == Json::objectValue);
780 }
781
782
783 void OrthancConfiguration::GetSection(OrthancConfiguration& target,
784 const std::string& key) const
785 {
786 assert(configuration_.type() == Json::objectValue);
787
788 target.path_ = GetPath(key);
789
790 if (!configuration_.isMember(key))
791 {
792 target.configuration_ = Json::objectValue;
793 }
794 else
795 {
796 if (configuration_[key].type() != Json::objectValue)
797 {
798 LogError("The configuration section \"" + target.path_ +
799 "\" is not an associative array as expected");
800
801 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
802 }
803
804 target.configuration_ = configuration_[key];
805 }
806 }
807
808
809 bool OrthancConfiguration::LookupStringValue(std::string& target,
810 const std::string& key) const
811 {
812 assert(configuration_.type() == Json::objectValue);
813
814 if (!configuration_.isMember(key))
815 {
816 return false;
817 }
818
819 if (configuration_[key].type() != Json::stringValue)
820 {
821 LogError("The configuration option \"" + GetPath(key) +
822 "\" is not a string as expected");
823
824 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
825 }
826
827 target = configuration_[key].asString();
828 return true;
829 }
830
831
832 bool OrthancConfiguration::LookupIntegerValue(int& target,
833 const std::string& key) const
834 {
835 assert(configuration_.type() == Json::objectValue);
836
837 if (!configuration_.isMember(key))
838 {
839 return false;
840 }
841
842 switch (configuration_[key].type())
843 {
844 case Json::intValue:
845 target = configuration_[key].asInt();
846 return true;
847
848 case Json::uintValue:
849 target = configuration_[key].asUInt();
850 return true;
851
852 default:
853 LogError("The configuration option \"" + GetPath(key) +
854 "\" is not an integer as expected");
855
856 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
857 }
858 }
859
860
861 bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
862 const std::string& key) const
863 {
864 int tmp;
865 if (!LookupIntegerValue(tmp, key))
866 {
867 return false;
868 }
869
870 if (tmp < 0)
871 {
872 LogError("The configuration option \"" + GetPath(key) +
873 "\" is not a positive integer as expected");
874
875 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
876 }
877 else
878 {
879 target = static_cast<unsigned int>(tmp);
880 return true;
881 }
882 }
883
884
885 bool OrthancConfiguration::LookupBooleanValue(bool& target,
886 const std::string& key) const
887 {
888 assert(configuration_.type() == Json::objectValue);
889
890 if (!configuration_.isMember(key))
891 {
892 return false;
893 }
894
895 if (configuration_[key].type() != Json::booleanValue)
896 {
897 LogError("The configuration option \"" + GetPath(key) +
898 "\" is not a Boolean as expected");
899
900 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
901 }
902
903 target = configuration_[key].asBool();
904 return true;
905 }
906
907
908 bool OrthancConfiguration::LookupFloatValue(float& target,
909 const std::string& key) const
910 {
911 assert(configuration_.type() == Json::objectValue);
912
913 if (!configuration_.isMember(key))
914 {
915 return false;
916 }
917
918 switch (configuration_[key].type())
919 {
920 case Json::realValue:
921 target = configuration_[key].asFloat();
922 return true;
923
924 case Json::intValue:
925 target = static_cast<float>(configuration_[key].asInt());
926 return true;
927
928 case Json::uintValue:
929 target = static_cast<float>(configuration_[key].asUInt());
930 return true;
931
932 default:
933 LogError("The configuration option \"" + GetPath(key) +
934 "\" is not an integer as expected");
935
936 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
937 }
938 }
939
940
941 bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
942 const std::string& key,
943 bool allowSingleString) const
944 {
945 assert(configuration_.type() == Json::objectValue);
946
947 target.clear();
948
949 if (!configuration_.isMember(key))
950 {
951 return false;
952 }
953
954 switch (configuration_[key].type())
955 {
956 case Json::arrayValue:
957 {
958 bool ok = true;
959
960 for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
961 {
962 if (configuration_[key][i].type() == Json::stringValue)
963 {
964 target.push_back(configuration_[key][i].asString());
965 }
966 else
967 {
968 ok = false;
969 }
970 }
971
972 if (ok)
973 {
974 return true;
975 }
976
977 break;
978 }
979
980 case Json::stringValue:
981 if (allowSingleString)
982 {
983 target.push_back(configuration_[key].asString());
984 return true;
985 }
986
987 break;
988
989 default:
990 break;
991 }
992
993 LogError("The configuration option \"" + GetPath(key) +
994 "\" is not a list of strings as expected");
995
996 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
997 }
998
999
1000 bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
1001 const std::string& key,
1002 bool allowSingleString) const
1003 {
1004 std::list<std::string> lst;
1005
1006 if (LookupListOfStrings(lst, key, allowSingleString))
1007 {
1008 target.clear();
1009
1010 for (std::list<std::string>::const_iterator
1011 it = lst.begin(); it != lst.end(); ++it)
1012 {
1013 target.insert(*it);
1014 }
1015
1016 return true;
1017 }
1018 else
1019 {
1020 return false;
1021 }
1022 }
1023
1024
1025 std::string OrthancConfiguration::GetStringValue(const std::string& key,
1026 const std::string& defaultValue) const
1027 {
1028 std::string tmp;
1029 if (LookupStringValue(tmp, key))
1030 {
1031 return tmp;
1032 }
1033 else
1034 {
1035 return defaultValue;
1036 }
1037 }
1038
1039
1040 int OrthancConfiguration::GetIntegerValue(const std::string& key,
1041 int defaultValue) const
1042 {
1043 int tmp;
1044 if (LookupIntegerValue(tmp, key))
1045 {
1046 return tmp;
1047 }
1048 else
1049 {
1050 return defaultValue;
1051 }
1052 }
1053
1054
1055 unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
1056 unsigned int defaultValue) const
1057 {
1058 unsigned int tmp;
1059 if (LookupUnsignedIntegerValue(tmp, key))
1060 {
1061 return tmp;
1062 }
1063 else
1064 {
1065 return defaultValue;
1066 }
1067 }
1068
1069
1070 bool OrthancConfiguration::GetBooleanValue(const std::string& key,
1071 bool defaultValue) const
1072 {
1073 bool tmp;
1074 if (LookupBooleanValue(tmp, key))
1075 {
1076 return tmp;
1077 }
1078 else
1079 {
1080 return defaultValue;
1081 }
1082 }
1083
1084
1085 float OrthancConfiguration::GetFloatValue(const std::string& key,
1086 float defaultValue) const
1087 {
1088 float tmp;
1089 if (LookupFloatValue(tmp, key))
1090 {
1091 return tmp;
1092 }
1093 else
1094 {
1095 return defaultValue;
1096 }
1097 }
1098
1099
1100 void OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
1101 const std::string& key) const
1102 {
1103 assert(configuration_.type() == Json::objectValue);
1104
1105 target.clear();
1106
1107 if (!configuration_.isMember(key))
1108 {
1109 return;
1110 }
1111
1112 if (configuration_[key].type() != Json::objectValue)
1113 {
1114 LogError("The configuration option \"" + GetPath(key) +
1115 "\" is not an object as expected");
1116
1117 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
1118 }
1119
1120 Json::Value::Members members = configuration_[key].getMemberNames();
1121
1122 for (size_t i = 0; i < members.size(); i++)
1123 {
1124 const Json::Value& value = configuration_[key][members[i]];
1125
1126 if (value.type() == Json::stringValue)
1127 {
1128 target[members[i]] = value.asString();
1129 }
1130 else
1131 {
1132 LogError("The configuration option \"" + GetPath(key) +
1133 "\" is not a dictionary mapping strings to strings");
1134
1135 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
1136 }
1137 }
1138 }
1139
1140
1141 void OrthancImage::Clear()
1142 {
1143 if (image_ != NULL)
1144 {
1145 OrthancPluginFreeImage(GetGlobalContext(), image_);
1146 image_ = NULL;
1147 }
1148 }
1149
1150
1151 void OrthancImage::CheckImageAvailable() const
1152 {
1153 if (image_ == NULL)
1154 {
1155 LogError("Trying to access a NULL image");
1156 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
1157 }
1158 }
1159
1160
1161 OrthancImage::OrthancImage() :
1162 image_(NULL)
1163 {
1164 }
1165
1166
1167 OrthancImage::OrthancImage(OrthancPluginImage* image) :
1168 image_(image)
1169 {
1170 }
1171
1172
1173 OrthancImage::OrthancImage(OrthancPluginPixelFormat format,
1174 uint32_t width,
1175 uint32_t height)
1176 {
1177 image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
1178
1179 if (image_ == NULL)
1180 {
1181 LogError("Cannot create an image");
1182 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
1183 }
1184 }
1185
1186
1187 OrthancImage::OrthancImage(OrthancPluginPixelFormat format,
1188 uint32_t width,
1189 uint32_t height,
1190 uint32_t pitch,
1191 void* buffer)
1192 {
1193 image_ = OrthancPluginCreateImageAccessor
1194 (GetGlobalContext(), format, width, height, pitch, buffer);
1195
1196 if (image_ == NULL)
1197 {
1198 LogError("Cannot create an image accessor");
1199 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
1200 }
1201 }
1202
1203 void OrthancImage::UncompressPngImage(const void* data,
1204 size_t size)
1205 {
1206 Clear();
1207
1208 image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
1209
1210 if (image_ == NULL)
1211 {
1212 LogError("Cannot uncompress a PNG image");
1213 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
1214 }
1215 }
1216
1217
1218 void OrthancImage::UncompressJpegImage(const void* data,
1219 size_t size)
1220 {
1221 Clear();
1222 image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
1223 if (image_ == NULL)
1224 {
1225 LogError("Cannot uncompress a JPEG image");
1226 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
1227 }
1228 }
1229
1230
1231 void OrthancImage::DecodeDicomImage(const void* data,
1232 size_t size,
1233 unsigned int frame)
1234 {
1235 Clear();
1236 image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
1237 if (image_ == NULL)
1238 {
1239 LogError("Cannot uncompress a DICOM image");
1240 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
1241 }
1242 }
1243
1244
1245 OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
1246 {
1247 CheckImageAvailable();
1248 return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
1249 }
1250
1251
1252 unsigned int OrthancImage::GetWidth() const
1253 {
1254 CheckImageAvailable();
1255 return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
1256 }
1257
1258
1259 unsigned int OrthancImage::GetHeight() const
1260 {
1261 CheckImageAvailable();
1262 return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
1263 }
1264
1265
1266 unsigned int OrthancImage::GetPitch() const
1267 {
1268 CheckImageAvailable();
1269 return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
1270 }
1271
1272
1273 void* OrthancImage::GetBuffer() const
1274 {
1275 CheckImageAvailable();
1276 return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
1277 }
1278
1279
1280 void OrthancImage::CompressPngImage(MemoryBuffer& target) const
1281 {
1282 CheckImageAvailable();
1283
1284 OrthancPlugins::MemoryBuffer answer;
1285 OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
1286 GetWidth(), GetHeight(), GetPitch(), GetBuffer());
1287
1288 target.Swap(answer);
1289 }
1290
1291
1292 void OrthancImage::CompressJpegImage(MemoryBuffer& target,
1293 uint8_t quality) const
1294 {
1295 CheckImageAvailable();
1296
1297 OrthancPlugins::MemoryBuffer answer;
1298 OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
1299 GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
1300
1301 target.Swap(answer);
1302 }
1303
1304
1305 void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
1306 {
1307 CheckImageAvailable();
1308 OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
1309 GetWidth(), GetHeight(), GetPitch(), GetBuffer());
1310 }
1311
1312
1313 void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
1314 uint8_t quality) const
1315 {
1316 CheckImageAvailable();
1317 OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
1318 GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
1319 }
1320
1321
1322 OrthancPluginImage* OrthancImage::Release()
1323 {
1324 CheckImageAvailable();
1325 OrthancPluginImage* tmp = image_;
1326 image_ = NULL;
1327 return tmp;
1328 }
1329
1330
1331 #if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
1332 FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
1333 matcher_(NULL),
1334 worklist_(worklist)
1335 {
1336 if (worklist_ == NULL)
1337 {
1338 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
1339 }
1340 }
1341
1342
1343 void FindMatcher::SetupDicom(const void* query,
1344 uint32_t size)
1345 {
1346 worklist_ = NULL;
1347
1348 matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
1349 if (matcher_ == NULL)
1350 {
1351 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
1352 }
1353 }
1354
1355
1356 FindMatcher::~FindMatcher()
1357 {
1358 // The "worklist_" field
1359
1360 if (matcher_ != NULL)
1361 {
1362 OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
1363 }
1364 }
1365
1366
1367
1368 bool FindMatcher::IsMatch(const void* dicom,
1369 uint32_t size) const
1370 {
1371 int32_t result;
1372
1373 if (matcher_ != NULL)
1374 {
1375 result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
1376 }
1377 else if (worklist_ != NULL)
1378 {
1379 result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
1380 }
1381 else
1382 {
1383 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
1384 }
1385
1386 if (result == 0)
1387 {
1388 return false;
1389 }
1390 else if (result == 1)
1391 {
1392 return true;
1393 }
1394 else
1395 {
1396 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
1397 }
1398 }
1399
1400 #endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
1401
1402 void AnswerJson(const Json::Value& value,
1403 OrthancPluginRestOutput* output)
1404 {
1405 std::string bodyString;
1406 WriteStyledJson(bodyString, value);
1407 OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
1408 }
1409
1410 void AnswerString(const std::string& answer,
1411 const char* mimeType,
1412 OrthancPluginRestOutput* output)
1413 {
1414 OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
1415 }
1416
1417 void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
1418 {
1419 OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
1420 }
1421
1422 void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
1423 {
1424 OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
1425 }
1426
1427 bool RestApiGetString(std::string& result,
1428 const std::string& uri,
1429 bool applyPlugins)
1430 {
1431 MemoryBuffer answer;
1432 if (!answer.RestApiGet(uri, applyPlugins))
1433 {
1434 return false;
1435 }
1436 else
1437 {
1438 answer.ToString(result);
1439 return true;
1440 }
1441 }
1442
1443 bool RestApiGetString(std::string& result,
1444 const std::string& uri,
1445 const std::map<std::string, std::string>& httpHeaders,
1446 bool applyPlugins)
1447 {
1448 MemoryBuffer answer;
1449 if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
1450 {
1451 return false;
1452 }
1453 else
1454 {
1455 answer.ToString(result);
1456 return true;
1457 }
1458 }
1459
1460
1461 bool RestApiGet(Json::Value& result,
1462 const std::string& uri,
1463 const std::map<std::string, std::string>& httpHeaders,
1464 bool applyPlugins)
1465 {
1466 MemoryBuffer answer;
1467
1468 if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
1469 {
1470 return false;
1471 }
1472 else
1473 {
1474 if (!answer.IsEmpty())
1475 {
1476 answer.ToJson(result);
1477 }
1478 return true;
1479 }
1480 }
1481
1482
1483 bool RestApiGet(Json::Value& result,
1484 const std::string& uri,
1485 bool applyPlugins)
1486 {
1487 MemoryBuffer answer;
1488
1489 if (!answer.RestApiGet(uri, applyPlugins))
1490 {
1491 return false;
1492 }
1493 else
1494 {
1495 if (!answer.IsEmpty())
1496 {
1497 answer.ToJson(result);
1498 }
1499 return true;
1500 }
1501 }
1502
1503
1504 bool RestApiPost(std::string& result,
1505 const std::string& uri,
1506 const void* body,
1507 size_t bodySize,
1508 bool applyPlugins)
1509 {
1510 MemoryBuffer answer;
1511
1512 if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
1513 {
1514 return false;
1515 }
1516 else
1517 {
1518 if (!answer.IsEmpty())
1519 {
1520 result.assign(answer.GetData(), answer.GetSize());
1521 }
1522 return true;
1523 }
1524 }
1525
1526
1527 bool RestApiPost(Json::Value& result,
1528 const std::string& uri,
1529 const void* body,
1530 size_t bodySize,
1531 bool applyPlugins)
1532 {
1533 MemoryBuffer answer;
1534
1535 if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
1536 {
1537 return false;
1538 }
1539 else
1540 {
1541 if (!answer.IsEmpty())
1542 {
1543 answer.ToJson(result);
1544 }
1545 return true;
1546 }
1547 }
1548
1549 #if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
1550 bool RestApiPost(Json::Value& result,
1551 const std::string& uri,
1552 const Json::Value& body,
1553 const std::map<std::string, std::string>& httpHeaders,
1554 bool applyPlugins)
1555 {
1556 MemoryBuffer answer;
1557
1558 if (!answer.RestApiPost(uri, body, httpHeaders, applyPlugins))
1559 {
1560 return false;
1561 }
1562 else
1563 {
1564 if (!answer.IsEmpty())
1565 {
1566 answer.ToJson(result);
1567 }
1568 return true;
1569 }
1570 }
1571 #endif
1572
1573
1574 bool RestApiPost(Json::Value& result,
1575 const std::string& uri,
1576 const Json::Value& body,
1577 bool applyPlugins)
1578 {
1579 std::string s;
1580 WriteFastJson(s, body);
1581 return RestApiPost(result, uri, s, applyPlugins);
1582 }
1583
1584
1585 bool RestApiPut(Json::Value& result,
1586 const std::string& uri,
1587 const void* body,
1588 size_t bodySize,
1589 bool applyPlugins)
1590 {
1591 MemoryBuffer answer;
1592
1593 if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
1594 {
1595 return false;
1596 }
1597 else
1598 {
1599 if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
1600 {
1601 answer.ToJson(result);
1602 }
1603 return true;
1604 }
1605 }
1606
1607
1608 bool RestApiPut(Json::Value& result,
1609 const std::string& uri,
1610 const Json::Value& body,
1611 bool applyPlugins)
1612 {
1613 std::string s;
1614 WriteFastJson(s, body);
1615 return RestApiPut(result, uri, s, applyPlugins);
1616 }
1617
1618
1619 bool RestApiDelete(const std::string& uri,
1620 bool applyPlugins)
1621 {
1622 OrthancPluginErrorCode error;
1623
1624 if (applyPlugins)
1625 {
1626 error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
1627 }
1628 else
1629 {
1630 error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
1631 }
1632
1633 if (error == OrthancPluginErrorCode_Success)
1634 {
1635 return true;
1636 }
1637 else if (error == OrthancPluginErrorCode_UnknownResource ||
1638 error == OrthancPluginErrorCode_InexistentItem)
1639 {
1640 return false;
1641 }
1642 else
1643 {
1644 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
1645 }
1646 }
1647
1648
1649 void ReportMinimalOrthancVersion(unsigned int major,
1650 unsigned int minor,
1651 unsigned int revision)
1652 {
1653 LogError("Your version of the Orthanc core (" +
1654 std::string(GetGlobalContext()->orthancVersion) +
1655 ") is too old to run this plugin (version " +
1656 boost::lexical_cast<std::string>(major) + "." +
1657 boost::lexical_cast<std::string>(minor) + "." +
1658 boost::lexical_cast<std::string>(revision) +
1659 " is required)");
1660 }
1661
1662 bool CheckMinimalVersion(const char* version,
1663 unsigned int major,
1664 unsigned int minor,
1665 unsigned int revision)
1666 {
1667 if (!strcmp(version, "mainline"))
1668 {
1669 // Assume compatibility with the mainline
1670 return true;
1671 }
1672
1673 // Parse the version
1674 int aa, bb, cc;
1675 if (
1676 #ifdef _MSC_VER
1677 sscanf_s
1678 #else
1679 sscanf
1680 #endif
1681 (version, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
1682 aa < 0 ||
1683 bb < 0 ||
1684 cc < 0)
1685 {
1686 return false;
1687 }
1688
1689 unsigned int a = static_cast<unsigned int>(aa);
1690 unsigned int b = static_cast<unsigned int>(bb);
1691 unsigned int c = static_cast<unsigned int>(cc);
1692
1693 // Check the major version number
1694
1695 if (a > major)
1696 {
1697 return true;
1698 }
1699
1700 if (a < major)
1701 {
1702 return false;
1703 }
1704
1705 // Check the minor version number
1706 assert(a == major);
1707
1708 if (b > minor)
1709 {
1710 return true;
1711 }
1712
1713 if (b < minor)
1714 {
1715 return false;
1716 }
1717
1718 // Check the patch level version number
1719 assert(a == major && b == minor);
1720
1721 if (c >= revision)
1722 {
1723 return true;
1724 }
1725 else
1726 {
1727 return false;
1728 }
1729 }
1730
1731
1732 bool CheckMinimalOrthancVersion(unsigned int major,
1733 unsigned int minor,
1734 unsigned int revision)
1735 {
1736 if (!HasGlobalContext())
1737 {
1738 LogError("Bad Orthanc context in the plugin");
1739 return false;
1740 }
1741
1742 return CheckMinimalVersion(GetGlobalContext()->orthancVersion,
1743 major, minor, revision);
1744 }
1745
1746
1747 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
1748 const char* AutodetectMimeType(const std::string& path)
1749 {
1750 const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
1751
1752 if (mime == NULL)
1753 {
1754 // Should never happen, just for safety
1755 return "application/octet-stream";
1756 }
1757 else
1758 {
1759 return mime;
1760 }
1761 }
1762 #endif
1763
1764
1765 #if HAS_ORTHANC_PLUGIN_PEERS == 1
1766 size_t OrthancPeers::GetPeerIndex(const std::string& name) const
1767 {
1768 size_t index;
1769 if (LookupName(index, name))
1770 {
1771 return index;
1772 }
1773 else
1774 {
1775 LogError("Inexistent peer: " + name);
1776 ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
1777 }
1778 }
1779
1780
1781 OrthancPeers::OrthancPeers() :
1782 peers_(NULL),
1783 timeout_(0)
1784 {
1785 peers_ = OrthancPluginGetPeers(GetGlobalContext());
1786
1787 if (peers_ == NULL)
1788 {
1789 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
1790 }
1791
1792 uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
1793
1794 for (uint32_t i = 0; i < count; i++)
1795 {
1796 const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
1797 if (name == NULL)
1798 {
1799 OrthancPluginFreePeers(GetGlobalContext(), peers_);
1800 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
1801 }
1802
1803 index_[name] = i;
1804 }
1805 }
1806
1807
1808 OrthancPeers::~OrthancPeers()
1809 {
1810 if (peers_ != NULL)
1811 {
1812 OrthancPluginFreePeers(GetGlobalContext(), peers_);
1813 }
1814 }
1815
1816
1817 bool OrthancPeers::LookupName(size_t& target,
1818 const std::string& name) const
1819 {
1820 Index::const_iterator found = index_.find(name);
1821
1822 if (found == index_.end())
1823 {
1824 return false;
1825 }
1826 else
1827 {
1828 target = found->second;
1829 return true;
1830 }
1831 }
1832
1833
1834 std::string OrthancPeers::GetPeerName(size_t index) const
1835 {
1836 if (index >= index_.size())
1837 {
1838 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
1839 }
1840 else
1841 {
1842 const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
1843 if (s == NULL)
1844 {
1845 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
1846 }
1847 else
1848 {
1849 return s;
1850 }
1851 }
1852 }
1853
1854
1855 std::string OrthancPeers::GetPeerUrl(size_t index) const
1856 {
1857 if (index >= index_.size())
1858 {
1859 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
1860 }
1861 else
1862 {
1863 const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
1864 if (s == NULL)
1865 {
1866 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
1867 }
1868 else
1869 {
1870 return s;
1871 }
1872 }
1873 }
1874
1875
1876 std::string OrthancPeers::GetPeerUrl(const std::string& name) const
1877 {
1878 return GetPeerUrl(GetPeerIndex(name));
1879 }
1880
1881
1882 bool OrthancPeers::LookupUserProperty(std::string& value,
1883 size_t index,
1884 const std::string& key) const
1885 {
1886 if (index >= index_.size())
1887 {
1888 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
1889 }
1890 else
1891 {
1892 const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
1893 if (s == NULL)
1894 {
1895 return false;
1896 }
1897 else
1898 {
1899 value.assign(s);
1900 return true;
1901 }
1902 }
1903 }
1904
1905
1906 bool OrthancPeers::LookupUserProperty(std::string& value,
1907 const std::string& peer,
1908 const std::string& key) const
1909 {
1910 return LookupUserProperty(value, GetPeerIndex(peer), key);
1911 }
1912
1913
1914 bool OrthancPeers::DoGet(MemoryBuffer& target,
1915 size_t index,
1916 const std::string& uri,
1917 const std::map<std::string, std::string>& headers) const
1918 {
1919 if (index >= index_.size())
1920 {
1921 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
1922 }
1923
1924 OrthancPlugins::MemoryBuffer answer;
1925 uint16_t status;
1926 PluginHttpHeaders pluginHeaders(headers);
1927
1928 OrthancPluginErrorCode code = OrthancPluginCallPeerApi
1929 (GetGlobalContext(), *answer, NULL, &status, peers_,
1930 static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
1931 pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);
1932
1933 if (code == OrthancPluginErrorCode_Success)
1934 {
1935 target.Swap(answer);
1936 return (status == 200);
1937 }
1938 else
1939 {
1940 return false;
1941 }
1942 }
1943
1944
1945 bool OrthancPeers::DoGet(MemoryBuffer& target,
1946 const std::string& name,
1947 const std::string& uri,
1948 const std::map<std::string, std::string>& headers) const
1949 {
1950 size_t index;
1951 return (LookupName(index, name) &&
1952 DoGet(target, index, uri, headers));
1953 }
1954
1955
1956 bool OrthancPeers::DoGet(Json::Value& target,
1957 size_t index,
1958 const std::string& uri,
1959 const std::map<std::string, std::string>& headers) const
1960 {
1961 MemoryBuffer buffer;
1962
1963 if (DoGet(buffer, index, uri, headers))
1964 {
1965 buffer.ToJson(target);
1966 return true;
1967 }
1968 else
1969 {
1970 return false;
1971 }
1972 }
1973
1974
1975 bool OrthancPeers::DoGet(Json::Value& target,
1976 const std::string& name,
1977 const std::string& uri,
1978 const std::map<std::string, std::string>& headers) const
1979 {
1980 MemoryBuffer buffer;
1981
1982 if (DoGet(buffer, name, uri, headers))
1983 {
1984 buffer.ToJson(target);
1985 return true;
1986 }
1987 else
1988 {
1989 return false;
1990 }
1991 }
1992
1993
1994 bool OrthancPeers::DoPost(MemoryBuffer& target,
1995 const std::string& name,
1996 const std::string& uri,
1997 const std::string& body,
1998 const std::map<std::string, std::string>& headers) const
1999 {
2000 size_t index;
2001 return (LookupName(index, name) &&
2002 DoPost(target, index, uri, body, headers));
2003 }
2004
2005
2006 bool OrthancPeers::DoPost(Json::Value& target,
2007 size_t index,
2008 const std::string& uri,
2009 const std::string& body,
2010 const std::map<std::string, std::string>& headers) const
2011 {
2012 MemoryBuffer buffer;
2013
2014 if (DoPost(buffer, index, uri, body, headers))
2015 {
2016 buffer.ToJson(target);
2017 return true;
2018 }
2019 else
2020 {
2021 return false;
2022 }
2023 }
2024
2025
2026 bool OrthancPeers::DoPost(Json::Value& target,
2027 const std::string& name,
2028 const std::string& uri,
2029 const std::string& body,
2030 const std::map<std::string, std::string>& headers) const
2031 {
2032 MemoryBuffer buffer;
2033
2034 if (DoPost(buffer, name, uri, body, headers))
2035 {
2036 buffer.ToJson(target);
2037 return true;
2038 }
2039 else
2040 {
2041 return false;
2042 }
2043 }
2044
2045
2046 bool OrthancPeers::DoPost(MemoryBuffer& target,
2047 size_t index,
2048 const std::string& uri,
2049 const std::string& body,
2050 const std::map<std::string, std::string>& headers) const
2051 {
2052 if (index >= index_.size())
2053 {
2054 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
2055 }
2056
2057 if (body.size() > 0xffffffffu)
2058 {
2059 LogError("Cannot handle body size > 4GB");
2060 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
2061 }
2062
2063 OrthancPlugins::MemoryBuffer answer;
2064 uint16_t status;
2065 PluginHttpHeaders pluginHeaders(headers);
2066
2067 OrthancPluginErrorCode code = OrthancPluginCallPeerApi
2068 (GetGlobalContext(), *answer, NULL, &status, peers_,
2069 static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
2070 pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);
2071
2072 if (code == OrthancPluginErrorCode_Success)
2073 {
2074 target.Swap(answer);
2075 return (status == 200);
2076 }
2077 else
2078 {
2079 return false;
2080 }
2081 }
2082
2083
2084 bool OrthancPeers::DoPut(size_t index,
2085 const std::string& uri,
2086 const std::string& body,
2087 const std::map<std::string, std::string>& headers) const
2088 {
2089 if (index >= index_.size())
2090 {
2091 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
2092 }
2093
2094 if (body.size() > 0xffffffffu)
2095 {
2096 LogError("Cannot handle body size > 4GB");
2097 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
2098 }
2099
2100 OrthancPlugins::MemoryBuffer answer;
2101 uint16_t status;
2102 PluginHttpHeaders pluginHeaders(headers);
2103
2104 OrthancPluginErrorCode code = OrthancPluginCallPeerApi
2105 (GetGlobalContext(), *answer, NULL, &status, peers_,
2106 static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
2107 pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);
2108
2109 if (code == OrthancPluginErrorCode_Success)
2110 {
2111 return (status == 200);
2112 }
2113 else
2114 {
2115 return false;
2116 }
2117 }
2118
2119
2120 bool OrthancPeers::DoPut(const std::string& name,
2121 const std::string& uri,
2122 const std::string& body,
2123 const std::map<std::string, std::string>& headers) const
2124 {
2125 size_t index;
2126 return (LookupName(index, name) &&
2127 DoPut(index, uri, body, headers));
2128 }
2129
2130
2131 bool OrthancPeers::DoDelete(size_t index,
2132 const std::string& uri,
2133 const std::map<std::string, std::string>& headers) const
2134 {
2135 if (index >= index_.size())
2136 {
2137 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
2138 }
2139
2140 OrthancPlugins::MemoryBuffer answer;
2141 uint16_t status;
2142 PluginHttpHeaders pluginHeaders(headers);
2143
2144 OrthancPluginErrorCode code = OrthancPluginCallPeerApi
2145 (GetGlobalContext(), *answer, NULL, &status, peers_,
2146 static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
2147 pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);
2148
2149 if (code == OrthancPluginErrorCode_Success)
2150 {
2151 return (status == 200);
2152 }
2153 else
2154 {
2155 return false;
2156 }
2157 }
2158
2159
2160 bool OrthancPeers::DoDelete(const std::string& name,
2161 const std::string& uri,
2162 const std::map<std::string, std::string>& headers) const
2163 {
2164 size_t index;
2165 return (LookupName(index, name) &&
2166 DoDelete(index, uri, headers));
2167 }
2168 #endif
2169
2170
2171
2172
2173
2174 /******************************************************************
2175 ** JOBS
2176 ******************************************************************/
2177
2178 #if HAS_ORTHANC_PLUGIN_JOB == 1
2179 void OrthancJob::CallbackFinalize(void* job)
2180 {
2181 if (job != NULL)
2182 {
2183 delete reinterpret_cast<OrthancJob*>(job);
2184 }
2185 }
2186
2187
2188 float OrthancJob::CallbackGetProgress(void* job)
2189 {
2190 assert(job != NULL);
2191
2192 try
2193 {
2194 return reinterpret_cast<OrthancJob*>(job)->progress_;
2195 }
2196 catch (...)
2197 {
2198 return 0;
2199 }
2200 }
2201
2202
2203 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
2204 static OrthancPluginErrorCode CopyStringToMemoryBuffer(OrthancPluginMemoryBuffer* target,
2205 const std::string& source)
2206 {
2207 if (OrthancPluginCreateMemoryBuffer(globalContext_, target, source.size()) != OrthancPluginErrorCode_Success)
2208 {
2209 return OrthancPluginErrorCode_NotEnoughMemory;
2210 }
2211 else
2212 {
2213 if (!source.empty())
2214 {
2215 memcpy(target->data, source.c_str(), source.size());
2216 }
2217
2218 return OrthancPluginErrorCode_Success;
2219 }
2220 }
2221 #endif
2222
2223
2224 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
2225 OrthancPluginErrorCode OrthancJob::CallbackGetContent(OrthancPluginMemoryBuffer* target,
2226 void* job)
2227 {
2228 assert(job != NULL);
2229 OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
2230 return CopyStringToMemoryBuffer(target, that.content_);
2231 }
2232 #else
2233 const char* OrthancJob::CallbackGetContent(void* job)
2234 {
2235 assert(job != NULL);
2236
2237 try
2238 {
2239 return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
2240 }
2241 catch (...)
2242 {
2243 return 0;
2244 }
2245 }
2246 #endif
2247
2248
2249 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
2250 int32_t OrthancJob::CallbackGetSerialized(OrthancPluginMemoryBuffer* target,
2251 void* job)
2252 {
2253 assert(job != NULL);
2254 OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
2255
2256 if (that.hasSerialized_)
2257 {
2258 if (CopyStringToMemoryBuffer(target, that.serialized_) == OrthancPluginErrorCode_Success)
2259 {
2260 return 1;
2261 }
2262 else
2263 {
2264 return -1;
2265 }
2266 }
2267 else
2268 {
2269 return 0;
2270 }
2271 }
2272 #else
2273 const char* OrthancJob::CallbackGetSerialized(void* job)
2274 {
2275 assert(job != NULL);
2276
2277 try
2278 {
2279 const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
2280
2281 if (tmp.hasSerialized_)
2282 {
2283 return tmp.serialized_.c_str();
2284 }
2285 else
2286 {
2287 return NULL;
2288 }
2289 }
2290 catch (...)
2291 {
2292 return 0;
2293 }
2294 }
2295 #endif
2296
2297
2298 OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
2299 {
2300 assert(job != NULL);
2301
2302 try
2303 {
2304 return reinterpret_cast<OrthancJob*>(job)->Step();
2305 }
2306 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
2307 {
2308 return OrthancPluginJobStepStatus_Failure;
2309 }
2310 catch (...)
2311 {
2312 return OrthancPluginJobStepStatus_Failure;
2313 }
2314 }
2315
2316
2317 OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
2318 OrthancPluginJobStopReason reason)
2319 {
2320 assert(job != NULL);
2321
2322 try
2323 {
2324 reinterpret_cast<OrthancJob*>(job)->Stop(reason);
2325 return OrthancPluginErrorCode_Success;
2326 }
2327 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
2328 {
2329 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
2330 }
2331 catch (...)
2332 {
2333 return OrthancPluginErrorCode_Plugin;
2334 }
2335 }
2336
2337
2338 OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
2339 {
2340 assert(job != NULL);
2341
2342 try
2343 {
2344 reinterpret_cast<OrthancJob*>(job)->Reset();
2345 return OrthancPluginErrorCode_Success;
2346 }
2347 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
2348 {
2349 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
2350 }
2351 catch (...)
2352 {
2353 return OrthancPluginErrorCode_Plugin;
2354 }
2355 }
2356
2357
2358 void OrthancJob::ClearContent()
2359 {
2360 Json::Value empty = Json::objectValue;
2361 UpdateContent(empty);
2362 }
2363
2364
2365 void OrthancJob::UpdateContent(const Json::Value& content)
2366 {
2367 if (content.type() != Json::objectValue)
2368 {
2369 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
2370 }
2371 else
2372 {
2373 WriteFastJson(content_, content);
2374 }
2375 }
2376
2377
2378 void OrthancJob::ClearSerialized()
2379 {
2380 hasSerialized_ = false;
2381 serialized_.clear();
2382 }
2383
2384
2385 void OrthancJob::UpdateSerialized(const Json::Value& serialized)
2386 {
2387 if (serialized.type() != Json::objectValue)
2388 {
2389 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
2390 }
2391 else
2392 {
2393 WriteFastJson(serialized_, serialized);
2394 hasSerialized_ = true;
2395 }
2396 }
2397
2398
2399 void OrthancJob::UpdateProgress(float progress)
2400 {
2401 if (progress < 0 ||
2402 progress > 1)
2403 {
2404 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
2405 }
2406
2407 progress_ = progress;
2408 }
2409
2410
2411 OrthancJob::OrthancJob(const std::string& jobType) :
2412 jobType_(jobType),
2413 progress_(0)
2414 {
2415 ClearContent();
2416 ClearSerialized();
2417 }
2418
2419
2420 OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
2421 {
2422 if (job == NULL)
2423 {
2424 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
2425 }
2426
2427 OrthancPluginJob* orthanc =
2428 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
2429 OrthancPluginCreateJob2
2430 #else
2431 OrthancPluginCreateJob
2432 #endif
2433 (GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
2434 CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
2435 CallbackStep, CallbackStop, CallbackReset);
2436
2437 if (orthanc == NULL)
2438 {
2439 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
2440 }
2441 else
2442 {
2443 return orthanc;
2444 }
2445 }
2446
2447
2448 std::string OrthancJob::Submit(OrthancJob* job,
2449 int priority)
2450 {
2451 if (job == NULL)
2452 {
2453 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
2454 }
2455
2456 OrthancPluginJob* orthanc = Create(job);
2457
2458 char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
2459
2460 if (id == NULL)
2461 {
2462 LogError("Plugin cannot submit job");
2463 OrthancPluginFreeJob(GetGlobalContext(), orthanc);
2464 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
2465 }
2466 else
2467 {
2468 std::string tmp(id);
2469 tmp.assign(id);
2470 OrthancPluginFreeString(GetGlobalContext(), id);
2471
2472 return tmp;
2473 }
2474 }
2475
2476
2477 void OrthancJob::SubmitAndWait(Json::Value& result,
2478 OrthancJob* job /* takes ownership */,
2479 int priority)
2480 {
2481 std::string id = Submit(job, priority);
2482
2483 for (;;)
2484 {
2485 boost::this_thread::sleep(boost::posix_time::milliseconds(100));
2486
2487 Json::Value status;
2488 if (!RestApiGet(status, "/jobs/" + id, false) ||
2489 !status.isMember("State") ||
2490 status["State"].type() != Json::stringValue)
2491 {
2492 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);
2493 }
2494
2495 const std::string state = status["State"].asString();
2496 if (state == "Success")
2497 {
2498 if (status.isMember("Content"))
2499 {
2500 result = status["Content"];
2501 }
2502 else
2503 {
2504 result = Json::objectValue;
2505 }
2506
2507 return;
2508 }
2509 else if (state == "Running")
2510 {
2511 continue;
2512 }
2513 else if (!status.isMember("ErrorCode") ||
2514 status["ErrorCode"].type() != Json::intValue)
2515 {
2516 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
2517 }
2518 else
2519 {
2520 if (!status.isMember("ErrorDescription") ||
2521 status["ErrorDescription"].type() != Json::stringValue)
2522 {
2523 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
2524 }
2525 else
2526 {
2527 #if HAS_ORTHANC_EXCEPTION == 1
2528 throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
2529 status["ErrorDescription"].asString());
2530 #else
2531 LogError("Exception while executing the job: " + status["ErrorDescription"].asString());
2532 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
2533 #endif
2534 }
2535 }
2536 }
2537 }
2538
2539
2540 void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
2541 const Json::Value& body,
2542 OrthancJob* job)
2543 {
2544 static const char* KEY_SYNCHRONOUS = "Synchronous";
2545 static const char* KEY_ASYNCHRONOUS = "Asynchronous";
2546 static const char* KEY_PRIORITY = "Priority";
2547
2548 boost::movelib::unique_ptr<OrthancJob> protection(job);
2549
2550 if (body.type() != Json::objectValue)
2551 {
2552 #if HAS_ORTHANC_EXCEPTION == 1
2553 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
2554 "Expected a JSON object in the body");
2555 #else
2556 LogError("Expected a JSON object in the body");
2557 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
2558 #endif
2559 }
2560
2561 bool synchronous = true;
2562
2563 if (body.isMember(KEY_SYNCHRONOUS))
2564 {
2565 if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
2566 {
2567 #if HAS_ORTHANC_EXCEPTION == 1
2568 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
2569 "Option \"" + std::string(KEY_SYNCHRONOUS) +
2570 "\" must be Boolean");
2571 #else
2572 LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
2573 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
2574 #endif
2575 }
2576 else
2577 {
2578 synchronous = body[KEY_SYNCHRONOUS].asBool();
2579 }
2580 }
2581
2582 if (body.isMember(KEY_ASYNCHRONOUS))
2583 {
2584 if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
2585 {
2586 #if HAS_ORTHANC_EXCEPTION == 1
2587 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
2588 "Option \"" + std::string(KEY_ASYNCHRONOUS) +
2589 "\" must be Boolean");
2590 #else
2591 LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
2592 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
2593 #endif
2594 }
2595 else
2596 {
2597 synchronous = !body[KEY_ASYNCHRONOUS].asBool();
2598 }
2599 }
2600
2601 int priority = 0;
2602
2603 if (body.isMember(KEY_PRIORITY))
2604 {
2605 if (body[KEY_PRIORITY].type() != Json::booleanValue)
2606 {
2607 #if HAS_ORTHANC_EXCEPTION == 1
2608 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
2609 "Option \"" + std::string(KEY_PRIORITY) +
2610 "\" must be an integer");
2611 #else
2612 LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
2613 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
2614 #endif
2615 }
2616 else
2617 {
2618 priority = !body[KEY_PRIORITY].asInt();
2619 }
2620 }
2621
2622 Json::Value result;
2623
2624 if (synchronous)
2625 {
2626 OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
2627 }
2628 else
2629 {
2630 std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
2631
2632 result = Json::objectValue;
2633 result["ID"] = id;
2634 result["Path"] = "/jobs/" + id;
2635 }
2636
2637 std::string s = result.toStyledString();
2638 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
2639 s.size(), "application/json");
2640 }
2641
2642 #endif
2643
2644
2645
2646
2647 /******************************************************************
2648 ** METRICS
2649 ******************************************************************/
2650
2651 #if HAS_ORTHANC_PLUGIN_METRICS == 1
2652 MetricsTimer::MetricsTimer(const char* name) :
2653 name_(name)
2654 {
2655 start_ = boost::posix_time::microsec_clock::universal_time();
2656 }
2657
2658 MetricsTimer::~MetricsTimer()
2659 {
2660 const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
2661 const boost::posix_time::time_duration diff = stop - start_;
2662 OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
2663 OrthancPluginMetricsType_Timer);
2664 }
2665 #endif
2666
2667
2668
2669
2670 /******************************************************************
2671 ** HTTP CLIENT
2672 ******************************************************************/
2673
2674 #if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
2675 class HttpClient::RequestBodyWrapper : public boost::noncopyable
2676 {
2677 private:
2678 static RequestBodyWrapper& GetObject(void* body)
2679 {
2680 assert(body != NULL);
2681 return *reinterpret_cast<RequestBodyWrapper*>(body);
2682 }
2683
2684 IRequestBody& body_;
2685 bool done_;
2686 std::string chunk_;
2687
2688 public:
2689 RequestBodyWrapper(IRequestBody& body) :
2690 body_(body),
2691 done_(false)
2692 {
2693 }
2694
2695 static uint8_t IsDone(void* body)
2696 {
2697 return GetObject(body).done_;
2698 }
2699
2700 static const void* GetChunkData(void* body)
2701 {
2702 return GetObject(body).chunk_.c_str();
2703 }
2704
2705 static uint32_t GetChunkSize(void* body)
2706 {
2707 return static_cast<uint32_t>(GetObject(body).chunk_.size());
2708 }
2709
2710 static OrthancPluginErrorCode Next(void* body)
2711 {
2712 RequestBodyWrapper& that = GetObject(body);
2713
2714 if (that.done_)
2715 {
2716 return OrthancPluginErrorCode_BadSequenceOfCalls;
2717 }
2718 else
2719 {
2720 try
2721 {
2722 that.done_ = !that.body_.ReadNextChunk(that.chunk_);
2723 return OrthancPluginErrorCode_Success;
2724 }
2725 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
2726 {
2727 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
2728 }
2729 catch (...)
2730 {
2731 return OrthancPluginErrorCode_Plugin;
2732 }
2733 }
2734 }
2735 };
2736
2737
2738 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
2739 static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
2740 const char* key,
2741 const char* value)
2742 {
2743 assert(answer != NULL && key != NULL && value != NULL);
2744
2745 try
2746 {
2747 reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
2748 return OrthancPluginErrorCode_Success;
2749 }
2750 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
2751 {
2752 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
2753 }
2754 catch (...)
2755 {
2756 return OrthancPluginErrorCode_Plugin;
2757 }
2758 }
2759 #endif
2760
2761
2762 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
2763 static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
2764 const void* data,
2765 uint32_t size)
2766 {
2767 assert(answer != NULL);
2768
2769 try
2770 {
2771 reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
2772 return OrthancPluginErrorCode_Success;
2773 }
2774 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
2775 {
2776 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
2777 }
2778 catch (...)
2779 {
2780 return OrthancPluginErrorCode_Plugin;
2781 }
2782 }
2783 #endif
2784
2785
2786 HttpClient::HttpClient() :
2787 httpStatus_(0),
2788 method_(OrthancPluginHttpMethod_Get),
2789 timeout_(0),
2790 pkcs11_(false),
2791 chunkedBody_(NULL),
2792 allowChunkedTransfers_(true)
2793 {
2794 }
2795
2796
2797 void HttpClient::AddHeaders(const HttpHeaders& headers)
2798 {
2799 for (HttpHeaders::const_iterator it = headers.begin();
2800 it != headers.end(); ++it)
2801 {
2802 headers_[it->first] = it->second;
2803 }
2804 }
2805
2806
2807 void HttpClient::SetCredentials(const std::string& username,
2808 const std::string& password)
2809 {
2810 username_ = username;
2811 password_ = password;
2812 }
2813
2814
2815 void HttpClient::ClearCredentials()
2816 {
2817 username_.clear();
2818 password_.clear();
2819 }
2820
2821
2822 void HttpClient::SetCertificate(const std::string& certificateFile,
2823 const std::string& keyFile,
2824 const std::string& keyPassword)
2825 {
2826 certificateFile_ = certificateFile;
2827 certificateKeyFile_ = keyFile;
2828 certificateKeyPassword_ = keyPassword;
2829 }
2830
2831
2832 void HttpClient::ClearCertificate()
2833 {
2834 certificateFile_.clear();
2835 certificateKeyFile_.clear();
2836 certificateKeyPassword_.clear();
2837 }
2838
2839
2840 void HttpClient::ClearBody()
2841 {
2842 fullBody_.clear();
2843 chunkedBody_ = NULL;
2844 }
2845
2846
2847 void HttpClient::SwapBody(std::string& body)
2848 {
2849 fullBody_.swap(body);
2850 chunkedBody_ = NULL;
2851 }
2852
2853
2854 void HttpClient::SetBody(const std::string& body)
2855 {
2856 fullBody_ = body;
2857 chunkedBody_ = NULL;
2858 }
2859
2860
2861 void HttpClient::SetBody(IRequestBody& body)
2862 {
2863 fullBody_.clear();
2864 chunkedBody_ = &body;
2865 }
2866
2867
2868 namespace
2869 {
2870 class HeadersWrapper : public boost::noncopyable
2871 {
2872 private:
2873 std::vector<const char*> headersKeys_;
2874 std::vector<const char*> headersValues_;
2875
2876 public:
2877 HeadersWrapper(const HttpClient::HttpHeaders& headers)
2878 {
2879 headersKeys_.reserve(headers.size());
2880 headersValues_.reserve(headers.size());
2881
2882 for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
2883 {
2884 headersKeys_.push_back(it->first.c_str());
2885 headersValues_.push_back(it->second.c_str());
2886 }
2887 }
2888
2889 void AddStaticString(const char* key,
2890 const char* value)
2891 {
2892 headersKeys_.push_back(key);
2893 headersValues_.push_back(value);
2894 }
2895
2896 uint32_t GetCount() const
2897 {
2898 return headersKeys_.size();
2899 }
2900
2901 const char* const* GetKeys() const
2902 {
2903 return headersKeys_.empty() ? NULL : &headersKeys_[0];
2904 }
2905
2906 const char* const* GetValues() const
2907 {
2908 return headersValues_.empty() ? NULL : &headersValues_[0];
2909 }
2910 };
2911
2912
2913 class MemoryRequestBody : public HttpClient::IRequestBody
2914 {
2915 private:
2916 std::string body_;
2917 bool done_;
2918
2919 public:
2920 MemoryRequestBody(const std::string& body) :
2921 body_(body),
2922 done_(false)
2923 {
2924 if (body_.empty())
2925 {
2926 done_ = true;
2927 }
2928 }
2929
2930 virtual bool ReadNextChunk(std::string& chunk)
2931 {
2932 if (done_)
2933 {
2934 return false;
2935 }
2936 else
2937 {
2938 chunk.swap(body_);
2939 done_ = true;
2940 return true;
2941 }
2942 }
2943 };
2944
2945
2946 // This class mimics Orthanc::ChunkedBuffer
2947 class ChunkedBuffer : public boost::noncopyable
2948 {
2949 private:
2950 typedef std::list<std::string*> Content;
2951
2952 Content content_;
2953 size_t size_;
2954
2955 public:
2956 ChunkedBuffer() :
2957 size_(0)
2958 {
2959 }
2960
2961 ~ChunkedBuffer()
2962 {
2963 Clear();
2964 }
2965
2966 void Clear()
2967 {
2968 for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
2969 {
2970 assert(*it != NULL);
2971 delete *it;
2972 }
2973
2974 size_ = 0;
2975 content_.clear();
2976 }
2977
2978 /**
2979 * Since Orthanc 1.9.3, this function also clears the content of
2980 * the ChunkedBuffer in order to mimic the behavior of the
2981 * original class "Orthanc::ChunkedBuffer". This prevents the
2982 * forgetting of calling "Clear()" in order to reduce memory
2983 * consumption.
2984 **/
2985 void Flatten(std::string& target)
2986 {
2987 target.resize(size_);
2988
2989 size_t pos = 0;
2990
2991 for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
2992 {
2993 assert(*it != NULL);
2994 size_t s = (*it)->size();
2995
2996 if (s != 0)
2997 {
2998 memcpy(&target[pos], (*it)->c_str(), s);
2999 pos += s;
3000 }
3001
3002 delete *it;
3003 }
3004
3005 assert(pos == target.size());
3006
3007 size_ = 0;
3008 content_.clear();
3009 }
3010
3011 void AddChunk(const void* data,
3012 size_t size)
3013 {
3014 content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
3015 size_ += size;
3016 }
3017
3018 void AddChunk(const std::string& chunk)
3019 {
3020 content_.push_back(new std::string(chunk));
3021 size_ += chunk.size();
3022 }
3023 };
3024
3025
3026 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
3027 class MemoryAnswer : public HttpClient::IAnswer
3028 {
3029 private:
3030 HttpClient::HttpHeaders headers_;
3031 ChunkedBuffer body_;
3032
3033 public:
3034 const HttpClient::HttpHeaders& GetHeaders() const
3035 {
3036 return headers_;
3037 }
3038
3039 ChunkedBuffer& GetBody()
3040 {
3041 return body_;
3042 }
3043
3044 virtual void AddHeader(const std::string& key,
3045 const std::string& value)
3046 {
3047 headers_[key] = value;
3048 }
3049
3050 virtual void AddChunk(const void* data,
3051 size_t size)
3052 {
3053 body_.AddChunk(data, size);
3054 }
3055 };
3056 #endif
3057 }
3058
3059
3060 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
3061 void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
3062 IAnswer& answer,
3063 IRequestBody& body) const
3064 {
3065 HeadersWrapper h(headers_);
3066
3067 if (method_ == OrthancPluginHttpMethod_Post ||
3068 method_ == OrthancPluginHttpMethod_Put)
3069 {
3070 // Automatically set the "Transfer-Encoding" header if absent
3071 bool found = false;
3072
3073 for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
3074 {
3075 if (boost::iequals(it->first, "Transfer-Encoding"))
3076 {
3077 found = true;
3078 break;
3079 }
3080 }
3081
3082 if (!found)
3083 {
3084 h.AddStaticString("Transfer-Encoding", "chunked");
3085 }
3086 }
3087
3088 RequestBodyWrapper request(body);
3089
3090 OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
3091 GetGlobalContext(),
3092 &answer,
3093 AnswerAddChunkCallback,
3094 AnswerAddHeaderCallback,
3095 &httpStatus,
3096 method_,
3097 url_.c_str(),
3098 h.GetCount(),
3099 h.GetKeys(),
3100 h.GetValues(),
3101 &request,
3102 RequestBodyWrapper::IsDone,
3103 RequestBodyWrapper::GetChunkData,
3104 RequestBodyWrapper::GetChunkSize,
3105 RequestBodyWrapper::Next,
3106 username_.empty() ? NULL : username_.c_str(),
3107 password_.empty() ? NULL : password_.c_str(),
3108 timeout_,
3109 certificateFile_.empty() ? NULL : certificateFile_.c_str(),
3110 certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
3111 certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
3112 pkcs11_ ? 1 : 0);
3113
3114 if (error != OrthancPluginErrorCode_Success)
3115 {
3116 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
3117 }
3118 }
3119 #endif
3120
3121
3122 void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
3123 HttpHeaders& answerHeaders,
3124 std::string& answerBody,
3125 const std::string& body) const
3126 {
3127 HeadersWrapper headers(headers_);
3128
3129 MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
3130
3131 if (body.size() > 0xffffffffu)
3132 {
3133 LogError("Cannot handle body size > 4GB");
3134 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
3135 }
3136
3137 OrthancPluginErrorCode error = OrthancPluginHttpClient(
3138 GetGlobalContext(),
3139 *answerBodyBuffer,
3140 *answerHeadersBuffer,
3141 &httpStatus,
3142 method_,
3143 url_.c_str(),
3144 headers.GetCount(),
3145 headers.GetKeys(),
3146 headers.GetValues(),
3147 body.empty() ? NULL : body.c_str(),
3148 body.size(),
3149 username_.empty() ? NULL : username_.c_str(),
3150 password_.empty() ? NULL : password_.c_str(),
3151 timeout_,
3152 certificateFile_.empty() ? NULL : certificateFile_.c_str(),
3153 certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
3154 certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
3155 pkcs11_ ? 1 : 0);
3156
3157 if (error != OrthancPluginErrorCode_Success)
3158 {
3159 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
3160 }
3161
3162 Json::Value v;
3163 answerHeadersBuffer.ToJson(v);
3164
3165 if (v.type() != Json::objectValue)
3166 {
3167 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
3168 }
3169
3170 Json::Value::Members members = v.getMemberNames();
3171 answerHeaders.clear();
3172
3173 for (size_t i = 0; i < members.size(); i++)
3174 {
3175 const Json::Value& h = v[members[i]];
3176 if (h.type() != Json::stringValue)
3177 {
3178 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
3179 }
3180 else
3181 {
3182 answerHeaders[members[i]] = h.asString();
3183 }
3184 }
3185
3186 answerBodyBuffer.ToString(answerBody);
3187 }
3188
3189
3190 void HttpClient::Execute(IAnswer& answer)
3191 {
3192 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
3193 if (allowChunkedTransfers_)
3194 {
3195 if (chunkedBody_ != NULL)
3196 {
3197 ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
3198 }
3199 else
3200 {
3201 MemoryRequestBody wrapper(fullBody_);
3202 ExecuteWithStream(httpStatus_, answer, wrapper);
3203 }
3204
3205 return;
3206 }
3207 #endif
3208
3209 // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
3210 // transfers are disabled. This results in higher memory usage
3211 // (all chunks from the answer body are sent at once)
3212
3213 HttpHeaders answerHeaders;
3214 std::string answerBody;
3215 Execute(answerHeaders, answerBody);
3216
3217 for (HttpHeaders::const_iterator it = answerHeaders.begin();
3218 it != answerHeaders.end(); ++it)
3219 {
3220 answer.AddHeader(it->first, it->second);
3221 }
3222
3223 if (!answerBody.empty())
3224 {
3225 answer.AddChunk(answerBody.c_str(), answerBody.size());
3226 }
3227 }
3228
3229
3230 void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
3231 std::string& answerBody /* out */)
3232 {
3233 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
3234 if (allowChunkedTransfers_)
3235 {
3236 MemoryAnswer answer;
3237 Execute(answer);
3238 answerHeaders = answer.GetHeaders();
3239 answer.GetBody().Flatten(answerBody);
3240 return;
3241 }
3242 #endif
3243
3244 // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
3245 // transfers are disabled. This results in higher memory usage
3246 // (all chunks from the request body are sent at once)
3247
3248 if (chunkedBody_ != NULL)
3249 {
3250 ChunkedBuffer buffer;
3251
3252 std::string chunk;
3253 while (chunkedBody_->ReadNextChunk(chunk))
3254 {
3255 buffer.AddChunk(chunk);
3256 }
3257
3258 std::string body;
3259 buffer.Flatten(body);
3260
3261 ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
3262 }
3263 else
3264 {
3265 ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
3266 }
3267 }
3268
3269
3270 void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
3271 Json::Value& answerBody /* out */)
3272 {
3273 std::string body;
3274 Execute(answerHeaders, body);
3275
3276 if (!ReadJson(answerBody, body))
3277 {
3278 LogError("Cannot convert HTTP answer body to JSON");
3279 ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
3280 }
3281 }
3282
3283
3284 void HttpClient::Execute()
3285 {
3286 HttpHeaders answerHeaders;
3287 std::string body;
3288 Execute(answerHeaders, body);
3289 }
3290
3291 #endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
3292
3293
3294
3295
3296
3297 /******************************************************************
3298 ** CHUNKED HTTP SERVER
3299 ******************************************************************/
3300
3301 namespace Internals
3302 {
3303 void NullRestCallback(OrthancPluginRestOutput* output,
3304 const char* url,
3305 const OrthancPluginHttpRequest* request)
3306 {
3307 }
3308
3309 IChunkedRequestReader *NullChunkedRestCallback(const char* url,
3310 const OrthancPluginHttpRequest* request)
3311 {
3312 return NULL;
3313 }
3314
3315
3316 #if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
3317
3318 OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
3319 OrthancPluginServerChunkedRequestReader* reader,
3320 const void* data,
3321 uint32_t size)
3322 {
3323 try
3324 {
3325 if (reader == NULL)
3326 {
3327 return OrthancPluginErrorCode_InternalError;
3328 }
3329
3330 reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
3331 return OrthancPluginErrorCode_Success;
3332 }
3333 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3334 {
3335 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3336 }
3337 catch (boost::bad_lexical_cast&)
3338 {
3339 return OrthancPluginErrorCode_BadFileFormat;
3340 }
3341 catch (...)
3342 {
3343 return OrthancPluginErrorCode_Plugin;
3344 }
3345 }
3346
3347
3348 OrthancPluginErrorCode ChunkedRequestReaderExecute(
3349 OrthancPluginServerChunkedRequestReader* reader,
3350 OrthancPluginRestOutput* output)
3351 {
3352 try
3353 {
3354 if (reader == NULL)
3355 {
3356 return OrthancPluginErrorCode_InternalError;
3357 }
3358
3359 reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
3360 return OrthancPluginErrorCode_Success;
3361 }
3362 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3363 {
3364 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3365 }
3366 catch (boost::bad_lexical_cast&)
3367 {
3368 return OrthancPluginErrorCode_BadFileFormat;
3369 }
3370 catch (...)
3371 {
3372 return OrthancPluginErrorCode_Plugin;
3373 }
3374 }
3375
3376
3377 void ChunkedRequestReaderFinalize(
3378 OrthancPluginServerChunkedRequestReader* reader)
3379 {
3380 if (reader != NULL)
3381 {
3382 delete reinterpret_cast<IChunkedRequestReader*>(reader);
3383 }
3384 }
3385
3386 #else
3387
3388 OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
3389 const char* url,
3390 const OrthancPluginHttpRequest* request,
3391 RestCallback GetHandler,
3392 ChunkedRestCallback PostHandler,
3393 RestCallback DeleteHandler,
3394 ChunkedRestCallback PutHandler)
3395 {
3396 try
3397 {
3398 std::string allowed;
3399
3400 if (GetHandler != Internals::NullRestCallback)
3401 {
3402 allowed += "GET";
3403 }
3404
3405 if (PostHandler != Internals::NullChunkedRestCallback)
3406 {
3407 if (!allowed.empty())
3408 {
3409 allowed += ",";
3410 }
3411
3412 allowed += "POST";
3413 }
3414
3415 if (DeleteHandler != Internals::NullRestCallback)
3416 {
3417 if (!allowed.empty())
3418 {
3419 allowed += ",";
3420 }
3421
3422 allowed += "DELETE";
3423 }
3424
3425 if (PutHandler != Internals::NullChunkedRestCallback)
3426 {
3427 if (!allowed.empty())
3428 {
3429 allowed += ",";
3430 }
3431
3432 allowed += "PUT";
3433 }
3434
3435 switch (request->method)
3436 {
3437 case OrthancPluginHttpMethod_Get:
3438 if (GetHandler == Internals::NullRestCallback)
3439 {
3440 OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
3441 }
3442 else
3443 {
3444 GetHandler(output, url, request);
3445 }
3446
3447 break;
3448
3449 case OrthancPluginHttpMethod_Post:
3450 if (PostHandler == Internals::NullChunkedRestCallback)
3451 {
3452 OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
3453 }
3454 else
3455 {
3456 boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
3457 if (reader.get() == NULL)
3458 {
3459 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
3460 }
3461 else
3462 {
3463 reader->AddChunk(request->body, request->bodySize);
3464 reader->Execute(output);
3465 }
3466 }
3467
3468 break;
3469
3470 case OrthancPluginHttpMethod_Delete:
3471 if (DeleteHandler == Internals::NullRestCallback)
3472 {
3473 OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
3474 }
3475 else
3476 {
3477 DeleteHandler(output, url, request);
3478 }
3479
3480 break;
3481
3482 case OrthancPluginHttpMethod_Put:
3483 if (PutHandler == Internals::NullChunkedRestCallback)
3484 {
3485 OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
3486 }
3487 else
3488 {
3489 boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
3490 if (reader.get() == NULL)
3491 {
3492 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
3493 }
3494 else
3495 {
3496 reader->AddChunk(request->body, request->bodySize);
3497 reader->Execute(output);
3498 }
3499 }
3500
3501 break;
3502
3503 default:
3504 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
3505 }
3506
3507 return OrthancPluginErrorCode_Success;
3508 }
3509 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3510 {
3511 #if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
3512 if (HasGlobalContext() &&
3513 e.HasDetails())
3514 {
3515 // The "false" instructs Orthanc not to log the detailed
3516 // error message. This is to avoid duplicating the details,
3517 // because "OrthancException" already does it on construction.
3518 OrthancPluginSetHttpErrorDetails
3519 (GetGlobalContext(), output, e.GetDetails(), false);
3520 }
3521 #endif
3522
3523 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3524 }
3525 catch (boost::bad_lexical_cast&)
3526 {
3527 return OrthancPluginErrorCode_BadFileFormat;
3528 }
3529 catch (...)
3530 {
3531 return OrthancPluginErrorCode_Plugin;
3532 }
3533 }
3534 #endif
3535 }
3536
3537
3538 #if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
3539 OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
3540 OrthancPluginStorageCommitmentFailureReason* target,
3541 void* rawHandler,
3542 const char* sopClassUid,
3543 const char* sopInstanceUid)
3544 {
3545 assert(target != NULL &&
3546 rawHandler != NULL);
3547
3548 try
3549 {
3550 IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
3551 *target = handler.Lookup(sopClassUid, sopInstanceUid);
3552 return OrthancPluginErrorCode_Success;
3553 }
3554 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3555 {
3556 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3557 }
3558 catch (...)
3559 {
3560 return OrthancPluginErrorCode_Plugin;
3561 }
3562 }
3563 #endif
3564
3565
3566 #if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
3567 void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
3568 {
3569 assert(rawHandler != NULL);
3570 delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
3571 }
3572 #endif
3573
3574
3575 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
3576 DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
3577 toFree_(false),
3578 instance_(instance)
3579 {
3580 }
3581 #else
3582 DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
3583 toFree_(false),
3584 instance_(instance)
3585 {
3586 }
3587 #endif
3588
3589
3590 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
3591 DicomInstance::DicomInstance(const void* buffer,
3592 size_t size) :
3593 toFree_(true),
3594 instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
3595 {
3596 if (instance_ == NULL)
3597 {
3598 ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
3599 }
3600 }
3601 #endif
3602
3603
3604 DicomInstance::~DicomInstance()
3605 {
3606 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
3607 if (toFree_ &&
3608 instance_ != NULL)
3609 {
3610 OrthancPluginFreeDicomInstance(
3611 GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
3612 }
3613 #endif
3614 }
3615
3616
3617 std::string DicomInstance::GetRemoteAet() const
3618 {
3619 const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
3620 if (s == NULL)
3621 {
3622 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
3623 }
3624 else
3625 {
3626 return std::string(s);
3627 }
3628 }
3629
3630
3631 void DicomInstance::GetJson(Json::Value& target) const
3632 {
3633 OrthancString s;
3634 s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
3635 s.ToJson(target);
3636 }
3637
3638
3639 void DicomInstance::GetSimplifiedJson(Json::Value& target) const
3640 {
3641 OrthancString s;
3642 s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
3643 s.ToJson(target);
3644 }
3645
3646
3647 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
3648 std::string DicomInstance::GetTransferSyntaxUid() const
3649 {
3650 OrthancString s;
3651 s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
3652
3653 std::string result;
3654 s.ToString(result);
3655 return result;
3656 }
3657 #endif
3658
3659
3660 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
3661 bool DicomInstance::HasPixelData() const
3662 {
3663 int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
3664 if (result < 0)
3665 {
3666 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
3667 }
3668 else
3669 {
3670 return (result != 0);
3671 }
3672 }
3673 #endif
3674
3675
3676 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
3677 void DicomInstance::GetRawFrame(std::string& target,
3678 unsigned int frameIndex) const
3679 {
3680 MemoryBuffer buffer;
3681 OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
3682 GetGlobalContext(), *buffer, instance_, frameIndex);
3683
3684 if (code == OrthancPluginErrorCode_Success)
3685 {
3686 buffer.ToString(target);
3687 }
3688 else
3689 {
3690 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
3691 }
3692 }
3693 #endif
3694
3695
3696 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
3697 OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
3698 {
3699 OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
3700 GetGlobalContext(), instance_, frameIndex);
3701
3702 if (image == NULL)
3703 {
3704 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
3705 }
3706 else
3707 {
3708 return new OrthancImage(image);
3709 }
3710 }
3711 #endif
3712
3713
3714 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
3715 void DicomInstance::Serialize(std::string& target) const
3716 {
3717 MemoryBuffer buffer;
3718 OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
3719 GetGlobalContext(), *buffer, instance_);
3720
3721 if (code == OrthancPluginErrorCode_Success)
3722 {
3723 buffer.ToString(target);
3724 }
3725 else
3726 {
3727 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
3728 }
3729 }
3730 #endif
3731
3732
3733 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
3734 DicomInstance* DicomInstance::Transcode(const void* buffer,
3735 size_t size,
3736 const std::string& transferSyntax)
3737 {
3738 OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
3739 GetGlobalContext(), buffer, size, transferSyntax.c_str());
3740
3741 if (instance == NULL)
3742 {
3743 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
3744 }
3745 else
3746 {
3747 boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
3748 result->toFree_ = true;
3749 return result.release();
3750 }
3751 }
3752 #endif
3753
3754
3755 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3756 static std::vector<std::string> WebDavConvertPath(uint32_t pathSize,
3757 const char* const* pathItems)
3758 {
3759 std::vector<std::string> result(pathSize);
3760
3761 for (uint32_t i = 0; i < pathSize; i++)
3762 {
3763 result[i] = pathItems[i];
3764 }
3765
3766 return result;
3767 }
3768 #endif
3769
3770
3771 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3772 static OrthancPluginErrorCode WebDavIsExistingFolder(uint8_t* isExisting,
3773 uint32_t pathSize,
3774 const char* const* pathItems,
3775 void* payload)
3776 {
3777 IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
3778
3779 try
3780 {
3781 *isExisting = (that.IsExistingFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
3782 return OrthancPluginErrorCode_Success;
3783 }
3784 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3785 {
3786 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3787 }
3788 catch (...)
3789 {
3790 return OrthancPluginErrorCode_Plugin;
3791 }
3792 }
3793 #endif
3794
3795
3796 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3797 static OrthancPluginErrorCode WebDavListFolder(uint8_t* isExisting,
3798 OrthancPluginWebDavCollection* collection,
3799 OrthancPluginWebDavAddFile addFile,
3800 OrthancPluginWebDavAddFolder addFolder,
3801 uint32_t pathSize,
3802 const char* const* pathItems,
3803 void* payload)
3804 {
3805 IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
3806
3807 try
3808 {
3809 std::list<IWebDavCollection::FileInfo> files;
3810 std::list<IWebDavCollection::FolderInfo> subfolders;
3811
3812 if (!that.ListFolder(files, subfolders, WebDavConvertPath(pathSize, pathItems)))
3813 {
3814 *isExisting = 0;
3815 }
3816 else
3817 {
3818 *isExisting = 1;
3819
3820 for (std::list<IWebDavCollection::FileInfo>::const_iterator
3821 it = files.begin(); it != files.end(); ++it)
3822 {
3823 OrthancPluginErrorCode code = addFile(
3824 collection, it->GetName().c_str(), it->GetContentSize(),
3825 it->GetMimeType().c_str(), it->GetDateTime().c_str());
3826
3827 if (code != OrthancPluginErrorCode_Success)
3828 {
3829 return code;
3830 }
3831 }
3832
3833 for (std::list<IWebDavCollection::FolderInfo>::const_iterator it =
3834 subfolders.begin(); it != subfolders.end(); ++it)
3835 {
3836 OrthancPluginErrorCode code = addFolder(
3837 collection, it->GetName().c_str(), it->GetDateTime().c_str());
3838
3839 if (code != OrthancPluginErrorCode_Success)
3840 {
3841 return code;
3842 }
3843 }
3844 }
3845
3846 return OrthancPluginErrorCode_Success;
3847 }
3848 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3849 {
3850 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3851 }
3852 catch (...)
3853 {
3854 return OrthancPluginErrorCode_Plugin;
3855 }
3856 }
3857 #endif
3858
3859
3860 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3861 static OrthancPluginErrorCode WebDavRetrieveFile(OrthancPluginWebDavCollection* collection,
3862 OrthancPluginWebDavRetrieveFile retrieveFile,
3863 uint32_t pathSize,
3864 const char* const* pathItems,
3865 void* payload)
3866 {
3867 IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
3868
3869 try
3870 {
3871 std::string content, mime, dateTime;
3872
3873 if (that.GetFile(content, mime, dateTime, WebDavConvertPath(pathSize, pathItems)))
3874 {
3875 return retrieveFile(collection, content.empty() ? NULL : content.c_str(),
3876 content.size(), mime.c_str(), dateTime.c_str());
3877 }
3878 else
3879 {
3880 // Inexisting file
3881 return OrthancPluginErrorCode_Success;
3882 }
3883 }
3884 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3885 {
3886 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3887 }
3888 catch (...)
3889 {
3890 return OrthancPluginErrorCode_InternalError;
3891 }
3892 }
3893 #endif
3894
3895
3896 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3897 static OrthancPluginErrorCode WebDavStoreFileCallback(uint8_t* isReadOnly, /* out */
3898 uint32_t pathSize,
3899 const char* const* pathItems,
3900 const void* data,
3901 uint64_t size,
3902 void* payload)
3903 {
3904 IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
3905
3906 try
3907 {
3908 if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
3909 {
3910 ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
3911 }
3912
3913 *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data,
3914 static_cast<size_t>(size)) ? 1 : 0);
3915 return OrthancPluginErrorCode_Success;
3916 }
3917 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3918 {
3919 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3920 }
3921 catch (...)
3922 {
3923 return OrthancPluginErrorCode_InternalError;
3924 }
3925 }
3926 #endif
3927
3928
3929 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3930 static OrthancPluginErrorCode WebDavCreateFolderCallback(uint8_t* isReadOnly, /* out */
3931 uint32_t pathSize,
3932 const char* const* pathItems,
3933 void* payload)
3934 {
3935 IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
3936
3937 try
3938 {
3939 *isReadOnly = (that.CreateFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
3940 return OrthancPluginErrorCode_Success;
3941 }
3942 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3943 {
3944 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3945 }
3946 catch (...)
3947 {
3948 return OrthancPluginErrorCode_InternalError;
3949 }
3950 }
3951 #endif
3952
3953
3954 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3955 static OrthancPluginErrorCode WebDavDeleteItemCallback(uint8_t* isReadOnly, /* out */
3956 uint32_t pathSize,
3957 const char* const* pathItems,
3958 void* payload)
3959 {
3960 IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
3961
3962 try
3963 {
3964 *isReadOnly = (that.DeleteItem(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
3965 return OrthancPluginErrorCode_Success;
3966 }
3967 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
3968 {
3969 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
3970 }
3971 catch (...)
3972 {
3973 return OrthancPluginErrorCode_InternalError;
3974 }
3975 }
3976 #endif
3977
3978
3979 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
3980 void IWebDavCollection::Register(const std::string& uri,
3981 IWebDavCollection& collection)
3982 {
3983 OrthancPluginErrorCode code = OrthancPluginRegisterWebDavCollection(
3984 GetGlobalContext(), uri.c_str(), WebDavIsExistingFolder, WebDavListFolder, WebDavRetrieveFile,
3985 WebDavStoreFileCallback, WebDavCreateFolderCallback, WebDavDeleteItemCallback, &collection);
3986
3987 if (code != OrthancPluginErrorCode_Success)
3988 {
3989 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
3990 }
3991 }
3992 #endif
3993
3994 void GetHttpHeaders(std::map<std::string, std::string>& result, const OrthancPluginHttpRequest* request)
3995 {
3996 result.clear();
3997
3998 for (uint32_t i = 0; i < request->headersCount; ++i)
3999 {
4000 result[request->headersKeys[i]] = request->headersValues[i];
4001 }
4002 }
4003 }