Mercurial > hg > orthanc-java
comparison Plugin/Plugin.cpp @ 0:3ecef5782f2c
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 18 Oct 2023 17:59:44 +0200 |
parents | |
children | 9032ffb3a7d5 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3ecef5782f2c |
---|---|
1 /** | |
2 * SPDX-FileCopyrightText: 2023 Sebastien Jodogne, UCLouvain, Belgium | |
3 * SPDX-License-Identifier: GPL-3.0-or-later | |
4 */ | |
5 | |
6 /** | |
7 * Java plugin for Orthanc | |
8 * Copyright (C) 2023 Sebastien Jodogne, UCLouvain, Belgium | |
9 * | |
10 * This program is free software: you can redistribute it and/or | |
11 * modify it under the terms of the GNU General Public License as | |
12 * published by the Free Software Foundation, either version 3 of the | |
13 * License, or (at your option) any later version. | |
14 * | |
15 * This program is distributed in the hope that it will be useful, but | |
16 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 * General Public License for more details. | |
19 * | |
20 * You should have received a copy of the GNU General Public License | |
21 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
22 **/ | |
23 | |
24 | |
25 #include <orthanc/OrthancCPlugin.h> | |
26 | |
27 #include <cassert> | |
28 #include <iostream> | |
29 #include <jni.h> | |
30 #include <list> | |
31 #include <map> | |
32 #include <memory> | |
33 #include <set> | |
34 #include <stdexcept> | |
35 #include <vector> | |
36 | |
37 #include <json/reader.h> | |
38 | |
39 #include "Mutex.h" | |
40 | |
41 static OrthancPluginContext* context_ = NULL; | |
42 | |
43 | |
44 | |
45 static const char* JAVA_EXCEPTION_CLASS = "be/uclouvain/orthanc/OrthancException"; | |
46 | |
47 class JavaVirtualMachine : public NonCopyable | |
48 { | |
49 private: | |
50 JavaVM *jvm_; | |
51 | |
52 public: | |
53 JavaVirtualMachine(const std::string& classPath) | |
54 { | |
55 std::string classPathOption = "-Djava.class.path=" + classPath; | |
56 | |
57 std::vector<JavaVMOption> options; | |
58 options.resize(2); | |
59 options[0].optionString = const_cast<char*>(classPathOption.c_str()); | |
60 options[0].extraInfo = NULL; | |
61 options[1].optionString = const_cast<char*>("-Xcheck:jni"); | |
62 options[1].extraInfo = NULL; | |
63 | |
64 JavaVMInitArgs vm_args; | |
65 vm_args.version = JNI_VERSION_1_6; | |
66 vm_args.nOptions = options.size(); | |
67 vm_args.options = (options.empty() ? NULL : &options[0]); | |
68 vm_args.ignoreUnrecognized = false; | |
69 | |
70 JNIEnv* env = NULL; | |
71 jint res = JNI_CreateJavaVM(&jvm_, (void **) &env, &vm_args); | |
72 if (res != JNI_OK || | |
73 jvm_ == NULL || | |
74 env == NULL) | |
75 { | |
76 throw std::runtime_error("Cannot create the JVM"); | |
77 } | |
78 } | |
79 | |
80 ~JavaVirtualMachine() | |
81 { | |
82 jvm_->DestroyJavaVM(); | |
83 } | |
84 | |
85 JavaVM& GetValue() | |
86 { | |
87 assert(jvm_ != NULL); | |
88 return *jvm_; | |
89 } | |
90 }; | |
91 | |
92 | |
93 class JavaEnvironment : public NonCopyable | |
94 { | |
95 private: | |
96 JavaVM *jvm_; | |
97 JNIEnv *env_; | |
98 | |
99 public: | |
100 JavaEnvironment(JNIEnv* env) : | |
101 jvm_(NULL), | |
102 env_(env) | |
103 { | |
104 if (env_ == NULL) | |
105 { | |
106 throw std::runtime_error("Null pointer"); | |
107 } | |
108 } | |
109 | |
110 JavaEnvironment(JavaVirtualMachine& jvm) : | |
111 jvm_(&jvm.GetValue()) | |
112 { | |
113 jint status = jvm_->GetEnv((void **) &env_, JNI_VERSION_1_6); | |
114 | |
115 switch (status) | |
116 { | |
117 case JNI_OK: | |
118 break; | |
119 | |
120 case JNI_EDETACHED: | |
121 { | |
122 jint code = jvm_->AttachCurrentThread((void **) &env_, NULL); | |
123 if (code != JNI_OK) | |
124 { | |
125 throw std::runtime_error("Cannot attach thread"); | |
126 } | |
127 break; | |
128 } | |
129 | |
130 case JNI_EVERSION: | |
131 throw std::runtime_error("JNI version not supported"); | |
132 | |
133 default: | |
134 throw std::runtime_error("Not implemented"); | |
135 } | |
136 | |
137 if (env_ == NULL) | |
138 { | |
139 throw std::runtime_error("Error inside JNI"); | |
140 } | |
141 } | |
142 | |
143 ~JavaEnvironment() | |
144 { | |
145 if (jvm_ != NULL) | |
146 { | |
147 jvm_->DetachCurrentThread(); | |
148 } | |
149 } | |
150 | |
151 void CheckException() | |
152 { | |
153 if (env_->ExceptionCheck() == JNI_TRUE) | |
154 { | |
155 env_->ExceptionClear(); | |
156 throw std::runtime_error("An exception has occurred in Java"); | |
157 } | |
158 } | |
159 | |
160 JNIEnv& GetValue() | |
161 { | |
162 assert(env_ != NULL); | |
163 return *env_; | |
164 } | |
165 | |
166 void RunGarbageCollector() | |
167 { | |
168 assert(env_ != NULL); | |
169 | |
170 jclass system = FindClass("java/lang/System"); | |
171 | |
172 jmethodID runFinalization = env_->GetStaticMethodID(system, "gc", "()V"); | |
173 if (runFinalization != NULL) | |
174 { | |
175 env_->CallStaticVoidMethod(system, runFinalization); | |
176 CheckException(); | |
177 } | |
178 else | |
179 { | |
180 throw std::runtime_error("Cannot run garbage collector"); | |
181 } | |
182 } | |
183 | |
184 jclass FindClass(const std::string& fqn) | |
185 { | |
186 jclass c = GetValue().FindClass(fqn.c_str()); | |
187 | |
188 if (c == NULL) | |
189 { | |
190 throw std::runtime_error("Unable to find class: " + fqn); | |
191 } | |
192 else | |
193 { | |
194 return c; | |
195 } | |
196 } | |
197 | |
198 jclass GetObjectClass(jobject obj) | |
199 { | |
200 jclass c = GetValue().GetObjectClass(obj); | |
201 | |
202 if (c == NULL) | |
203 { | |
204 throw std::runtime_error("Unable to get class of object"); | |
205 } | |
206 else | |
207 { | |
208 return c; | |
209 } | |
210 } | |
211 | |
212 jmethodID GetMethodID(jclass c, | |
213 const std::string& method, | |
214 const std::string& signature) | |
215 { | |
216 jmethodID m = GetValue().GetMethodID(c, method.c_str(), signature.c_str()); | |
217 | |
218 if (m == NULL) | |
219 { | |
220 throw std::runtime_error("Unable to locate method in class"); | |
221 } | |
222 else | |
223 { | |
224 return m; | |
225 } | |
226 } | |
227 | |
228 jobject ConstructJavaWrapper(const std::string& fqn, | |
229 void* nativeObject) | |
230 { | |
231 jclass cls = FindClass(fqn); | |
232 jmethodID constructor = GetMethodID(cls, "<init>", "(J)V"); | |
233 jobject obj = env_->NewObject(cls, constructor, reinterpret_cast<intptr_t>(nativeObject)); | |
234 | |
235 if (obj == NULL) | |
236 { | |
237 throw std::runtime_error("Cannot create Java wrapper around C/C++ object: " + fqn); | |
238 } | |
239 else | |
240 { | |
241 return obj; | |
242 } | |
243 } | |
244 | |
245 jbyteArray ConstructByteArray(const size_t size, | |
246 const void* data) | |
247 { | |
248 assert(env_ != NULL); | |
249 jbyteArray obj = env_->NewByteArray(size); | |
250 if (obj == NULL) | |
251 { | |
252 throw std::runtime_error("Cannot create a byte array"); | |
253 } | |
254 else | |
255 { | |
256 if (size > 0) | |
257 { | |
258 env_->SetByteArrayRegion(obj, 0, size, reinterpret_cast<const jbyte*>(data)); | |
259 } | |
260 | |
261 return obj; | |
262 } | |
263 } | |
264 | |
265 jbyteArray ConstructByteArray(const std::string& data) | |
266 { | |
267 return ConstructByteArray(data.size(), data.c_str()); | |
268 } | |
269 | |
270 void RegisterNatives(const std::string& fqn, | |
271 const std::vector<JNINativeMethod>& methods) | |
272 { | |
273 if (!methods.empty()) | |
274 { | |
275 if (env_->RegisterNatives(FindClass(fqn), &methods[0], methods.size()) < 0) | |
276 { | |
277 throw std::runtime_error("Unable to register the native methods"); | |
278 } | |
279 } | |
280 } | |
281 | |
282 void ThrowException(const std::string& fqn, | |
283 const std::string& message) | |
284 { | |
285 if (GetValue().ThrowNew(FindClass(fqn), message.c_str()) != 0) | |
286 { | |
287 std::string message = "Cannot throw exception " + fqn; | |
288 OrthancPluginLogError(context_, message.c_str()); | |
289 } | |
290 } | |
291 | |
292 void ThrowException(const std::string& message) | |
293 { | |
294 ThrowException(JAVA_EXCEPTION_CLASS, message); | |
295 } | |
296 | |
297 void ThrowException(OrthancPluginErrorCode code) | |
298 { | |
299 ThrowException(JAVA_EXCEPTION_CLASS, OrthancPluginGetErrorDescription(context_, code)); | |
300 } | |
301 | |
302 jobject ConstructEnumValue(const std::string& fqn, | |
303 int value) | |
304 { | |
305 assert(env_ != NULL); | |
306 jclass cls = FindClass(fqn); | |
307 | |
308 std::string signature = "(I)L" + fqn + ";"; | |
309 jmethodID constructor = env_->GetStaticMethodID(cls, "getInstance", signature.c_str()); | |
310 if (constructor != NULL) | |
311 { | |
312 jobject obj = env_->CallStaticObjectMethod(cls, constructor, static_cast<jint>(value)); | |
313 CheckException(); | |
314 return obj; | |
315 } | |
316 else | |
317 { | |
318 char buf[16]; | |
319 sprintf(buf, "%d", value); | |
320 throw std::runtime_error("Cannot create enumeration value: " + fqn + " " + buf); | |
321 } | |
322 } | |
323 | |
324 | |
325 static void ThrowException(JNIEnv* env, | |
326 const std::string& fqn, | |
327 const std::string& message) | |
328 { | |
329 JavaEnvironment e(env); | |
330 e.ThrowException(fqn, message); | |
331 } | |
332 | |
333 static void ThrowException(JNIEnv* env, | |
334 const std::string& message) | |
335 { | |
336 JavaEnvironment e(env); | |
337 e.ThrowException(message); | |
338 } | |
339 | |
340 static void ThrowException(JNIEnv* env, | |
341 OrthancPluginErrorCode code) | |
342 { | |
343 JavaEnvironment e(env); | |
344 e.ThrowException(code); | |
345 } | |
346 }; | |
347 | |
348 | |
349 static std::unique_ptr<JavaVirtualMachine> java_; | |
350 | |
351 | |
352 | |
353 class JavaString : public NonCopyable | |
354 { | |
355 private: | |
356 JNIEnv* env_; | |
357 jstring javaStr_; | |
358 const char* cStr_; | |
359 jboolean isCopy_; | |
360 | |
361 public: | |
362 JavaString(JNIEnv* env, | |
363 jstring javaStr) : | |
364 env_(env), | |
365 javaStr_(javaStr) | |
366 { | |
367 if (env == NULL || | |
368 javaStr == NULL) | |
369 { | |
370 throw std::runtime_error("Null pointer"); | |
371 } | |
372 | |
373 cStr_ = env_->GetStringUTFChars(javaStr_, &isCopy_); | |
374 if (cStr_ == NULL) | |
375 { | |
376 throw std::runtime_error("Cannot read string"); | |
377 } | |
378 } | |
379 | |
380 ~JavaString() | |
381 { | |
382 /** | |
383 * "The ReleaseString-Chars call is necessary whether | |
384 * GetStringChars has set isCopy to JNI_TRUE or JNI_FALSE." | |
385 * https://stackoverflow.com/a/5863081 | |
386 **/ | |
387 env_->ReleaseStringUTFChars(javaStr_, cStr_); | |
388 } | |
389 | |
390 const char* GetValue() const | |
391 { | |
392 return cStr_; | |
393 } | |
394 }; | |
395 | |
396 | |
397 class JavaBytes : public NonCopyable | |
398 { | |
399 private: | |
400 JNIEnv* env_; | |
401 jbyteArray bytes_; | |
402 jbyte* data_; | |
403 jsize size_; | |
404 jboolean isCopy_; | |
405 | |
406 public: | |
407 JavaBytes(JNIEnv* env, | |
408 jbyteArray bytes) : | |
409 env_(env), | |
410 bytes_(bytes) | |
411 { | |
412 if (env == NULL || | |
413 bytes == NULL) | |
414 { | |
415 throw std::runtime_error("Null pointer"); | |
416 } | |
417 | |
418 size_ = env->GetArrayLength(bytes); | |
419 | |
420 if (size_ == 0) | |
421 { | |
422 data_ = NULL; | |
423 } | |
424 else | |
425 { | |
426 data_ = env->GetByteArrayElements(bytes_, &isCopy_); | |
427 if (data_ == NULL) | |
428 { | |
429 throw std::runtime_error("Cannot read array of bytes"); | |
430 } | |
431 } | |
432 } | |
433 | |
434 ~JavaBytes() | |
435 { | |
436 if (size_ > 0) | |
437 { | |
438 env_->ReleaseByteArrayElements(bytes_, data_, 0); | |
439 } | |
440 } | |
441 | |
442 const void* GetData() const | |
443 { | |
444 return data_; | |
445 } | |
446 | |
447 size_t GetSize() const | |
448 { | |
449 return size_; | |
450 } | |
451 }; | |
452 | |
453 | |
454 class OrthancString : public NonCopyable | |
455 { | |
456 private: | |
457 char* str_; | |
458 | |
459 public: | |
460 OrthancString(char* str) : | |
461 str_(str) | |
462 { | |
463 } | |
464 | |
465 ~OrthancString() | |
466 { | |
467 if (str_ != NULL) | |
468 { | |
469 OrthancPluginFreeString(context_, str_); | |
470 } | |
471 } | |
472 | |
473 const char* GetValue() const | |
474 { | |
475 return str_; | |
476 } | |
477 }; | |
478 | |
479 | |
480 class OrthancBytes : public NonCopyable | |
481 { | |
482 private: | |
483 OrthancPluginMemoryBuffer buffer_; | |
484 | |
485 public: | |
486 OrthancBytes() | |
487 { | |
488 buffer_.data = NULL; | |
489 buffer_.size = 0; | |
490 } | |
491 | |
492 ~OrthancBytes() | |
493 { | |
494 OrthancPluginFreeMemoryBuffer(context_, &buffer_); | |
495 } | |
496 | |
497 OrthancPluginMemoryBuffer* GetMemoryBuffer() | |
498 { | |
499 return &buffer_; | |
500 } | |
501 | |
502 const void* GetData() const | |
503 { | |
504 return buffer_.data; | |
505 } | |
506 | |
507 size_t GetSize() const | |
508 { | |
509 return buffer_.size; | |
510 } | |
511 }; | |
512 | |
513 | |
514 class JavaGlobalReference : public NonCopyable | |
515 { | |
516 private: | |
517 JavaVirtualMachine& jvm_; | |
518 jobject obj_; | |
519 | |
520 public: | |
521 JavaGlobalReference(JavaVirtualMachine& jvm, | |
522 jobject obj) : | |
523 jvm_(jvm), | |
524 obj_(NULL) | |
525 { | |
526 if (obj == NULL) | |
527 { | |
528 throw std::runtime_error("Null pointer"); | |
529 } | |
530 | |
531 JavaEnvironment env(jvm); | |
532 | |
533 obj_ = env.GetValue().NewGlobalRef(obj); | |
534 if (obj_ == NULL) | |
535 { | |
536 throw std::runtime_error("Cannot create global reference"); | |
537 } | |
538 } | |
539 | |
540 ~JavaGlobalReference() | |
541 { | |
542 assert(obj_ != NULL); | |
543 | |
544 try | |
545 { | |
546 JavaEnvironment env(jvm_); | |
547 env.GetValue().DeleteGlobalRef(obj_); | |
548 } | |
549 catch (std::runtime_error& e) | |
550 { | |
551 OrthancPluginLogError(context_, e.what()); | |
552 } | |
553 } | |
554 | |
555 jobject GetValue() | |
556 { | |
557 assert(obj_ != NULL); | |
558 return obj_; | |
559 } | |
560 }; | |
561 | |
562 | |
563 class LocalJavaObject : public NonCopyable | |
564 { | |
565 private: | |
566 JNIEnv* env_; | |
567 jobject obj_; | |
568 | |
569 public: | |
570 LocalJavaObject(JavaEnvironment& env, | |
571 jobject obj, | |
572 bool objCanBeNull = false) : | |
573 env_(&env.GetValue()), | |
574 obj_(obj) | |
575 { | |
576 if (!objCanBeNull && obj == NULL) | |
577 { | |
578 throw std::runtime_error("Null pointer"); | |
579 } | |
580 } | |
581 | |
582 ~LocalJavaObject() | |
583 { | |
584 env_->DeleteLocalRef(obj_); | |
585 } | |
586 | |
587 jobject GetValue() | |
588 { | |
589 return obj_; | |
590 } | |
591 | |
592 static LocalJavaObject* CreateArrayOfStrings(JavaEnvironment& env, | |
593 const std::vector<std::string>& items) | |
594 { | |
595 LocalJavaObject emptyString(env, env.GetValue().NewStringUTF("")); | |
596 | |
597 jobjectArray obj = env.GetValue().NewObjectArray( | |
598 items.size(), env.GetValue().FindClass("java/lang/String"), | |
599 emptyString.GetValue()); | |
600 | |
601 if (obj == NULL) | |
602 { | |
603 throw std::runtime_error("Cannot create an array of Java strings"); | |
604 } | |
605 else | |
606 { | |
607 std::unique_ptr<LocalJavaObject> result(new LocalJavaObject(env, obj)); | |
608 | |
609 for (size_t i = 0; i < items.size(); i++) | |
610 { | |
611 LocalJavaObject item(env, env.GetValue().NewStringUTF(items[i].c_str())); | |
612 env.GetValue().SetObjectArrayElement(obj, i, item.GetValue()); | |
613 } | |
614 | |
615 return result.release(); | |
616 } | |
617 } | |
618 | |
619 static LocalJavaObject* CreateDictionary(JavaEnvironment& env, | |
620 const std::map<std::string, std::string>& items) | |
621 { | |
622 // NB: In JNI, there are no generics. All the templated arguments | |
623 // are taken as instances of the "Object" base class. | |
624 | |
625 jclass cls = env.FindClass("java/util/HashMap"); | |
626 jmethodID constructor = env.GetMethodID(cls, "<init>", "()V"); | |
627 jmethodID setter = env.GetMethodID(cls, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); | |
628 jobject obj = env.GetValue().NewObject(cls, constructor); | |
629 | |
630 if (obj == NULL) | |
631 { | |
632 throw std::runtime_error("Cannot create a Java dictionary"); | |
633 } | |
634 else | |
635 { | |
636 std::unique_ptr<LocalJavaObject> result(new LocalJavaObject(env, obj)); | |
637 | |
638 for (std::map<std::string, std::string>::const_iterator it = items.begin(); it != items.end(); ++it) | |
639 { | |
640 LocalJavaObject key(env, env.GetValue().NewStringUTF(it->first.c_str())); | |
641 LocalJavaObject value(env, env.GetValue().NewStringUTF(it->second.c_str())); | |
642 LocalJavaObject previousValue(env, env.GetValue().CallObjectMethod(obj, setter, key.GetValue(), value.GetValue()), true); | |
643 env.CheckException(); | |
644 } | |
645 | |
646 return result.release(); | |
647 } | |
648 } | |
649 }; | |
650 | |
651 | |
652 | |
653 #include "NativeSDK.cpp" | |
654 | |
655 | |
656 | |
657 #define MAX_REST_CALLBACKS 10 | |
658 | |
659 class CallbacksConfiguration : public NonCopyable | |
660 { | |
661 private: | |
662 Mutex mutex_; | |
663 std::list<JavaGlobalReference*> onChangeCallbacks_; | |
664 std::vector<JavaGlobalReference*> onRestRequestCallbacks_; | |
665 | |
666 static void DestructCallbacks(std::list<JavaGlobalReference*>& lst) | |
667 { | |
668 for (std::list<JavaGlobalReference*>::iterator it = lst.begin(); it != lst.end(); ++it) | |
669 { | |
670 assert(*it != NULL); | |
671 delete *it; | |
672 } | |
673 } | |
674 | |
675 static void DestructCallbacks(std::vector<JavaGlobalReference*>& v) | |
676 { | |
677 for (size_t i = 0; i < v.size(); i++) | |
678 { | |
679 assert(v[i] != NULL); | |
680 delete v[i]; | |
681 } | |
682 } | |
683 | |
684 void CopyCallbacks(std::list<jobject>& target, | |
685 const std::list<JavaGlobalReference*>& lst) | |
686 { | |
687 Mutex::Locker locker(mutex_); | |
688 target.clear(); | |
689 | |
690 for (std::list<JavaGlobalReference*>::const_iterator it = lst.begin(); it != lst.end(); ++it) | |
691 { | |
692 assert(*it != NULL); | |
693 target.push_back((*it)->GetValue()); | |
694 } | |
695 } | |
696 | |
697 void AddCallback(std::list<JavaGlobalReference*>& lst, | |
698 JavaVirtualMachine& jvm, | |
699 jobject callback) | |
700 { | |
701 if (callback == NULL) | |
702 { | |
703 throw std::runtime_error("Null pointer"); | |
704 } | |
705 else | |
706 { | |
707 Mutex::Locker locker(mutex_); | |
708 lst.push_back(new JavaGlobalReference(jvm, callback)); | |
709 } | |
710 } | |
711 | |
712 public: | |
713 CallbacksConfiguration() | |
714 { | |
715 onRestRequestCallbacks_.reserve(MAX_REST_CALLBACKS); | |
716 } | |
717 | |
718 ~CallbacksConfiguration() | |
719 { | |
720 DestructCallbacks(onChangeCallbacks_); | |
721 DestructCallbacks(onRestRequestCallbacks_); | |
722 } | |
723 | |
724 void AddOnChangeCallback(JavaVirtualMachine& jvm, | |
725 jobject callback) | |
726 { | |
727 AddCallback(onChangeCallbacks_, jvm, callback); | |
728 } | |
729 | |
730 void GetOnChangeCallbacks(std::list<jobject>& target) | |
731 { | |
732 CopyCallbacks(target, onChangeCallbacks_); | |
733 } | |
734 | |
735 size_t AddOnRestRequestCallback(JavaVirtualMachine& jvm, | |
736 jobject callback) | |
737 { | |
738 if (callback == NULL) | |
739 { | |
740 throw std::runtime_error("Null pointer"); | |
741 } | |
742 else | |
743 { | |
744 Mutex::Locker locker(mutex_); | |
745 | |
746 if (onRestRequestCallbacks_.size() >= MAX_REST_CALLBACKS) | |
747 { | |
748 char buf[16]; | |
749 sprintf(buf, "%d", MAX_REST_CALLBACKS); | |
750 throw std::runtime_error("The Java plugin for Orthanc has been compiled for a maximum of " + | |
751 std::string(buf) + " REST callbacks"); | |
752 } | |
753 else | |
754 { | |
755 size_t result = onRestRequestCallbacks_.size(); | |
756 onRestRequestCallbacks_.push_back(new JavaGlobalReference(jvm, callback)); | |
757 return result; | |
758 } | |
759 } | |
760 } | |
761 | |
762 jobject GetOnRestCallback(size_t i) | |
763 { | |
764 Mutex::Locker locker(mutex_); | |
765 | |
766 if (i >= onRestRequestCallbacks_.size()) | |
767 { | |
768 throw std::runtime_error("Unknown REST callback"); | |
769 } | |
770 else | |
771 { | |
772 assert(onRestRequestCallbacks_[i] != NULL); | |
773 return onRestRequestCallbacks_[i]->GetValue(); | |
774 } | |
775 } | |
776 }; | |
777 | |
778 static std::unique_ptr<CallbacksConfiguration> callbacksConfiguration_; | |
779 | |
780 | |
781 | |
782 | |
783 template<size_t Index> | |
784 class RestCallbacksPool | |
785 { | |
786 private: | |
787 RestCallbacksPool<Index - 1> next_; | |
788 | |
789 static OrthancPluginErrorCode Callback(OrthancPluginRestOutput* output, | |
790 const char* uri, | |
791 const OrthancPluginHttpRequest* request) | |
792 { | |
793 try | |
794 { | |
795 jobject callback = callbacksConfiguration_->GetOnRestCallback(MAX_REST_CALLBACKS - Index); | |
796 if (callback == NULL) | |
797 { | |
798 throw std::runtime_error("Missing callback"); | |
799 } | |
800 | |
801 std::vector<std::string> groups; | |
802 groups.resize(request->groupsCount); | |
803 for (uint32_t i = 0; i < request->groupsCount; i++) | |
804 { | |
805 groups[i].assign(request->groups[i]); | |
806 } | |
807 | |
808 std::map<std::string, std::string> headers; | |
809 for (uint32_t i = 0; i < request->headersCount; i++) | |
810 { | |
811 headers[request->headersKeys[i]] = request->headersValues[i]; | |
812 } | |
813 | |
814 std::map<std::string, std::string> getParameters; | |
815 for (uint32_t i = 0; i < request->getCount; i++) | |
816 { | |
817 getParameters[request->getKeys[i]] = request->getValues[i]; | |
818 } | |
819 | |
820 JavaEnvironment env(*java_); | |
821 | |
822 LocalJavaObject joutput(env, env.ConstructJavaWrapper("be/uclouvain/orthanc/RestOutput", output)); | |
823 LocalJavaObject jmethod(env, env.ConstructEnumValue("be/uclouvain/orthanc/HttpMethod", request->method)); | |
824 LocalJavaObject juri(env, env.GetValue().NewStringUTF(uri == NULL ? "" : uri)); | |
825 std::unique_ptr<LocalJavaObject> jgroups(LocalJavaObject::CreateArrayOfStrings(env, groups)); | |
826 std::unique_ptr<LocalJavaObject> jheaders(LocalJavaObject::CreateDictionary(env, headers)); | |
827 std::unique_ptr<LocalJavaObject> jgetParameters(LocalJavaObject::CreateDictionary(env, getParameters)); | |
828 LocalJavaObject jbody(env, env.ConstructByteArray(request->bodySize, request->body)); | |
829 | |
830 jmethodID call = env.GetMethodID( | |
831 env.GetObjectClass(callback), "call", | |
832 "(Lbe/uclouvain/orthanc/RestOutput;Lbe/uclouvain/orthanc/HttpMethod;Ljava/lang/String;" | |
833 "[Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;[B)V"); | |
834 | |
835 env.GetValue().CallVoidMethod(callback, call, joutput.GetValue(), jmethod.GetValue(), juri.GetValue(), | |
836 jgroups->GetValue(), jheaders->GetValue(), jgetParameters->GetValue(), jbody.GetValue()); | |
837 env.CheckException(); | |
838 | |
839 return OrthancPluginErrorCode_Success; | |
840 } | |
841 catch (std::runtime_error& e) | |
842 { | |
843 OrthancPluginLogError(context_, e.what()); | |
844 return OrthancPluginErrorCode_Plugin; | |
845 } | |
846 catch (...) | |
847 { | |
848 OrthancPluginLogError(context_, "Caught native exception"); | |
849 return OrthancPluginErrorCode_Plugin; | |
850 } | |
851 } | |
852 | |
853 public: | |
854 OrthancPluginRestCallback GetCallback(size_t i) | |
855 { | |
856 if (i == 0) | |
857 { | |
858 return Callback; | |
859 } | |
860 else | |
861 { | |
862 return next_.GetCallback(i - 1); | |
863 } | |
864 } | |
865 }; | |
866 | |
867 template<> | |
868 class RestCallbacksPool<0> | |
869 { | |
870 public: | |
871 OrthancPluginRestCallback& GetCallback(size_t i) | |
872 { | |
873 throw std::runtime_error("Out of tuple"); | |
874 } | |
875 }; | |
876 | |
877 | |
878 static RestCallbacksPool<MAX_REST_CALLBACKS> restCallbacksPool_; | |
879 | |
880 | |
881 | |
882 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, | |
883 OrthancPluginResourceType resourceType, | |
884 const char* resourceId) | |
885 { | |
886 try | |
887 { | |
888 std::list<jobject> callbacks; | |
889 callbacksConfiguration_->GetOnChangeCallbacks(callbacks); | |
890 | |
891 if (!callbacks.empty()) | |
892 { | |
893 JavaEnvironment env(*java_); | |
894 | |
895 LocalJavaObject c(env, env.ConstructEnumValue("be/uclouvain/orthanc/ChangeType", changeType)); | |
896 LocalJavaObject r(env, env.ConstructEnumValue("be/uclouvain/orthanc/ResourceType", resourceType)); | |
897 LocalJavaObject s(env, env.GetValue().NewStringUTF(resourceId == NULL ? "" : resourceId)); | |
898 | |
899 for (std::list<jobject>::const_iterator | |
900 callback = callbacks.begin(); callback != callbacks.end(); ++callback) | |
901 { | |
902 assert(*callback != NULL); | |
903 | |
904 jmethodID call = env.GetMethodID( | |
905 env.GetObjectClass(*callback), "call", | |
906 "(Lbe/uclouvain/orthanc/ChangeType;Lbe/uclouvain/orthanc/ResourceType;Ljava/lang/String;)V"); | |
907 | |
908 env.GetValue().CallVoidMethod(*callback, call, c.GetValue(), r.GetValue(), s.GetValue()); | |
909 env.CheckException(); | |
910 } | |
911 } | |
912 | |
913 return OrthancPluginErrorCode_Success; | |
914 } | |
915 catch (std::runtime_error& e) | |
916 { | |
917 OrthancPluginLogError(context_, e.what()); | |
918 return OrthancPluginErrorCode_Plugin; | |
919 } | |
920 catch (...) | |
921 { | |
922 OrthancPluginLogError(context_, "Caught native exception"); | |
923 return OrthancPluginErrorCode_Plugin; | |
924 } | |
925 } | |
926 | |
927 | |
928 JNIEXPORT void RegisterOnChangeCallback(JNIEnv* env, jobject sdkObject, jobject callback) | |
929 { | |
930 try | |
931 { | |
932 callbacksConfiguration_->AddOnChangeCallback(*java_, callback); | |
933 } | |
934 catch (std::runtime_error& e) | |
935 { | |
936 JavaEnvironment::ThrowException(env, e.what()); | |
937 } | |
938 catch (...) | |
939 { | |
940 JavaEnvironment::ThrowException(env, OrthancPluginErrorCode_Plugin); | |
941 } | |
942 } | |
943 | |
944 | |
945 JNIEXPORT void RegisterOnRestRequestCallback(JNIEnv* env, jobject sdkObject, jstring regex, jobject callback) | |
946 { | |
947 try | |
948 { | |
949 JavaString cregex(env, regex); | |
950 size_t index = callbacksConfiguration_->AddOnRestRequestCallback(*java_, callback); | |
951 OrthancPluginRegisterRestCallbackNoLock(context_, cregex.GetValue(), restCallbacksPool_.GetCallback(index)); | |
952 } | |
953 catch (std::runtime_error& e) | |
954 { | |
955 JavaEnvironment::ThrowException(env, e.what()); | |
956 } | |
957 catch (...) | |
958 { | |
959 JavaEnvironment::ThrowException(env, OrthancPluginErrorCode_Plugin); | |
960 } | |
961 } | |
962 | |
963 | |
964 extern "C" | |
965 { | |
966 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) | |
967 { | |
968 context_ = context; | |
969 | |
970 /* Check the version of the Orthanc core */ | |
971 if (OrthancPluginCheckVersion(context) == 0) | |
972 { | |
973 char info[1024]; | |
974 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", | |
975 context->orthancVersion, | |
976 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, | |
977 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, | |
978 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); | |
979 OrthancPluginLogError(context, info); | |
980 return -1; | |
981 } | |
982 | |
983 try | |
984 { | |
985 { | |
986 // Sanity check to ensure that the compiler has created different callback functions | |
987 std::set<intptr_t> c; | |
988 for (unsigned int i = 0; i < MAX_REST_CALLBACKS; i++) | |
989 { | |
990 c.insert(reinterpret_cast<intptr_t>(restCallbacksPool_.GetCallback(i))); | |
991 } | |
992 | |
993 if (c.size() != MAX_REST_CALLBACKS) | |
994 { | |
995 throw std::runtime_error("The Java plugin has not been properly compiled"); | |
996 } | |
997 } | |
998 | |
999 java_.reset(new JavaVirtualMachine("TODO")); | |
1000 | |
1001 callbacksConfiguration_.reset(new CallbacksConfiguration); | |
1002 OrthancPluginRegisterOnChangeCallback(context_, OnChangeCallback); | |
1003 | |
1004 JavaEnvironment env(*java_); | |
1005 | |
1006 { | |
1007 std::vector<JNINativeMethod> methods; | |
1008 JNI_LoadNatives(methods); | |
1009 env.RegisterNatives("be/uclouvain/orthanc/NativeSDK", methods); | |
1010 } | |
1011 | |
1012 { | |
1013 std::vector<JNINativeMethod> methods; | |
1014 methods.push_back((JNINativeMethod) { | |
1015 const_cast<char*>("register"), | |
1016 const_cast<char*>("(Lbe/uclouvain/orthanc/Callbacks$OnChange;)V"), | |
1017 (void*) RegisterOnChangeCallback }); | |
1018 methods.push_back((JNINativeMethod) { | |
1019 const_cast<char*>("register"), | |
1020 const_cast<char*>("(Ljava/lang/String;Lbe/uclouvain/orthanc/Callbacks$OnRestRequest;)V"), | |
1021 (void*) RegisterOnRestRequestCallback }); | |
1022 env.RegisterNatives("be/uclouvain/orthanc/Callbacks", methods); | |
1023 } | |
1024 } | |
1025 catch (std::runtime_error& e) | |
1026 { | |
1027 OrthancPluginLogError(context, e.what()); | |
1028 return -1; | |
1029 } | |
1030 | |
1031 return 0; | |
1032 } | |
1033 | |
1034 | |
1035 ORTHANC_PLUGINS_API void OrthancPluginFinalize() | |
1036 { | |
1037 if (java_.get() != NULL) | |
1038 { | |
1039 callbacksConfiguration_.reset(NULL); | |
1040 | |
1041 try | |
1042 { | |
1043 JavaEnvironment env(*java_); | |
1044 env.RunGarbageCollector(); | |
1045 } | |
1046 catch (std::runtime_error& e) | |
1047 { | |
1048 OrthancPluginLogError(context_, e.what()); | |
1049 } | |
1050 | |
1051 java_.reset(NULL); | |
1052 } | |
1053 } | |
1054 | |
1055 | |
1056 ORTHANC_PLUGINS_API const char* OrthancPluginGetName() | |
1057 { | |
1058 return "java"; | |
1059 } | |
1060 | |
1061 | |
1062 ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() | |
1063 { | |
1064 return PLUGIN_VERSION; | |
1065 } | |
1066 } |