comparison Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp @ 0:3f1cf4a8e31f

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