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 }