Mercurial > hg > orthanc-java
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Plugin.cpp Wed Oct 18 17:59:44 2023 +0200 @@ -0,0 +1,1066 @@ +/** + * SPDX-FileCopyrightText: 2023 Sebastien Jodogne, UCLouvain, Belgium + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * Java plugin for Orthanc + * Copyright (C) 2023 Sebastien Jodogne, UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include <orthanc/OrthancCPlugin.h> + +#include <cassert> +#include <iostream> +#include <jni.h> +#include <list> +#include <map> +#include <memory> +#include <set> +#include <stdexcept> +#include <vector> + +#include <json/reader.h> + +#include "Mutex.h" + +static OrthancPluginContext* context_ = NULL; + + + +static const char* JAVA_EXCEPTION_CLASS = "be/uclouvain/orthanc/OrthancException"; + +class JavaVirtualMachine : public NonCopyable +{ +private: + JavaVM *jvm_; + +public: + JavaVirtualMachine(const std::string& classPath) + { + std::string classPathOption = "-Djava.class.path=" + classPath; + + std::vector<JavaVMOption> options; + options.resize(2); + options[0].optionString = const_cast<char*>(classPathOption.c_str()); + options[0].extraInfo = NULL; + options[1].optionString = const_cast<char*>("-Xcheck:jni"); + options[1].extraInfo = NULL; + + JavaVMInitArgs vm_args; + vm_args.version = JNI_VERSION_1_6; + vm_args.nOptions = options.size(); + vm_args.options = (options.empty() ? NULL : &options[0]); + vm_args.ignoreUnrecognized = false; + + JNIEnv* env = NULL; + jint res = JNI_CreateJavaVM(&jvm_, (void **) &env, &vm_args); + if (res != JNI_OK || + jvm_ == NULL || + env == NULL) + { + throw std::runtime_error("Cannot create the JVM"); + } + } + + ~JavaVirtualMachine() + { + jvm_->DestroyJavaVM(); + } + + JavaVM& GetValue() + { + assert(jvm_ != NULL); + return *jvm_; + } +}; + + +class JavaEnvironment : public NonCopyable +{ +private: + JavaVM *jvm_; + JNIEnv *env_; + +public: + JavaEnvironment(JNIEnv* env) : + jvm_(NULL), + env_(env) + { + if (env_ == NULL) + { + throw std::runtime_error("Null pointer"); + } + } + + JavaEnvironment(JavaVirtualMachine& jvm) : + jvm_(&jvm.GetValue()) + { + jint status = jvm_->GetEnv((void **) &env_, JNI_VERSION_1_6); + + switch (status) + { + case JNI_OK: + break; + + case JNI_EDETACHED: + { + jint code = jvm_->AttachCurrentThread((void **) &env_, NULL); + if (code != JNI_OK) + { + throw std::runtime_error("Cannot attach thread"); + } + break; + } + + case JNI_EVERSION: + throw std::runtime_error("JNI version not supported"); + + default: + throw std::runtime_error("Not implemented"); + } + + if (env_ == NULL) + { + throw std::runtime_error("Error inside JNI"); + } + } + + ~JavaEnvironment() + { + if (jvm_ != NULL) + { + jvm_->DetachCurrentThread(); + } + } + + void CheckException() + { + if (env_->ExceptionCheck() == JNI_TRUE) + { + env_->ExceptionClear(); + throw std::runtime_error("An exception has occurred in Java"); + } + } + + JNIEnv& GetValue() + { + assert(env_ != NULL); + return *env_; + } + + void RunGarbageCollector() + { + assert(env_ != NULL); + + jclass system = FindClass("java/lang/System"); + + jmethodID runFinalization = env_->GetStaticMethodID(system, "gc", "()V"); + if (runFinalization != NULL) + { + env_->CallStaticVoidMethod(system, runFinalization); + CheckException(); + } + else + { + throw std::runtime_error("Cannot run garbage collector"); + } + } + + jclass FindClass(const std::string& fqn) + { + jclass c = GetValue().FindClass(fqn.c_str()); + + if (c == NULL) + { + throw std::runtime_error("Unable to find class: " + fqn); + } + else + { + return c; + } + } + + jclass GetObjectClass(jobject obj) + { + jclass c = GetValue().GetObjectClass(obj); + + if (c == NULL) + { + throw std::runtime_error("Unable to get class of object"); + } + else + { + return c; + } + } + + jmethodID GetMethodID(jclass c, + const std::string& method, + const std::string& signature) + { + jmethodID m = GetValue().GetMethodID(c, method.c_str(), signature.c_str()); + + if (m == NULL) + { + throw std::runtime_error("Unable to locate method in class"); + } + else + { + return m; + } + } + + jobject ConstructJavaWrapper(const std::string& fqn, + void* nativeObject) + { + jclass cls = FindClass(fqn); + jmethodID constructor = GetMethodID(cls, "<init>", "(J)V"); + jobject obj = env_->NewObject(cls, constructor, reinterpret_cast<intptr_t>(nativeObject)); + + if (obj == NULL) + { + throw std::runtime_error("Cannot create Java wrapper around C/C++ object: " + fqn); + } + else + { + return obj; + } + } + + jbyteArray ConstructByteArray(const size_t size, + const void* data) + { + assert(env_ != NULL); + jbyteArray obj = env_->NewByteArray(size); + if (obj == NULL) + { + throw std::runtime_error("Cannot create a byte array"); + } + else + { + if (size > 0) + { + env_->SetByteArrayRegion(obj, 0, size, reinterpret_cast<const jbyte*>(data)); + } + + return obj; + } + } + + jbyteArray ConstructByteArray(const std::string& data) + { + return ConstructByteArray(data.size(), data.c_str()); + } + + void RegisterNatives(const std::string& fqn, + const std::vector<JNINativeMethod>& methods) + { + if (!methods.empty()) + { + if (env_->RegisterNatives(FindClass(fqn), &methods[0], methods.size()) < 0) + { + throw std::runtime_error("Unable to register the native methods"); + } + } + } + + void ThrowException(const std::string& fqn, + const std::string& message) + { + if (GetValue().ThrowNew(FindClass(fqn), message.c_str()) != 0) + { + std::string message = "Cannot throw exception " + fqn; + OrthancPluginLogError(context_, message.c_str()); + } + } + + void ThrowException(const std::string& message) + { + ThrowException(JAVA_EXCEPTION_CLASS, message); + } + + void ThrowException(OrthancPluginErrorCode code) + { + ThrowException(JAVA_EXCEPTION_CLASS, OrthancPluginGetErrorDescription(context_, code)); + } + + jobject ConstructEnumValue(const std::string& fqn, + int value) + { + assert(env_ != NULL); + jclass cls = FindClass(fqn); + + std::string signature = "(I)L" + fqn + ";"; + jmethodID constructor = env_->GetStaticMethodID(cls, "getInstance", signature.c_str()); + if (constructor != NULL) + { + jobject obj = env_->CallStaticObjectMethod(cls, constructor, static_cast<jint>(value)); + CheckException(); + return obj; + } + else + { + char buf[16]; + sprintf(buf, "%d", value); + throw std::runtime_error("Cannot create enumeration value: " + fqn + " " + buf); + } + } + + + static void ThrowException(JNIEnv* env, + const std::string& fqn, + const std::string& message) + { + JavaEnvironment e(env); + e.ThrowException(fqn, message); + } + + static void ThrowException(JNIEnv* env, + const std::string& message) + { + JavaEnvironment e(env); + e.ThrowException(message); + } + + static void ThrowException(JNIEnv* env, + OrthancPluginErrorCode code) + { + JavaEnvironment e(env); + e.ThrowException(code); + } +}; + + +static std::unique_ptr<JavaVirtualMachine> java_; + + + +class JavaString : public NonCopyable +{ +private: + JNIEnv* env_; + jstring javaStr_; + const char* cStr_; + jboolean isCopy_; + +public: + JavaString(JNIEnv* env, + jstring javaStr) : + env_(env), + javaStr_(javaStr) + { + if (env == NULL || + javaStr == NULL) + { + throw std::runtime_error("Null pointer"); + } + + cStr_ = env_->GetStringUTFChars(javaStr_, &isCopy_); + if (cStr_ == NULL) + { + throw std::runtime_error("Cannot read string"); + } + } + + ~JavaString() + { + /** + * "The ReleaseString-Chars call is necessary whether + * GetStringChars has set isCopy to JNI_TRUE or JNI_FALSE." + * https://stackoverflow.com/a/5863081 + **/ + env_->ReleaseStringUTFChars(javaStr_, cStr_); + } + + const char* GetValue() const + { + return cStr_; + } +}; + + +class JavaBytes : public NonCopyable +{ +private: + JNIEnv* env_; + jbyteArray bytes_; + jbyte* data_; + jsize size_; + jboolean isCopy_; + +public: + JavaBytes(JNIEnv* env, + jbyteArray bytes) : + env_(env), + bytes_(bytes) + { + if (env == NULL || + bytes == NULL) + { + throw std::runtime_error("Null pointer"); + } + + size_ = env->GetArrayLength(bytes); + + if (size_ == 0) + { + data_ = NULL; + } + else + { + data_ = env->GetByteArrayElements(bytes_, &isCopy_); + if (data_ == NULL) + { + throw std::runtime_error("Cannot read array of bytes"); + } + } + } + + ~JavaBytes() + { + if (size_ > 0) + { + env_->ReleaseByteArrayElements(bytes_, data_, 0); + } + } + + const void* GetData() const + { + return data_; + } + + size_t GetSize() const + { + return size_; + } +}; + + +class OrthancString : public NonCopyable +{ +private: + char* str_; + +public: + OrthancString(char* str) : + str_(str) + { + } + + ~OrthancString() + { + if (str_ != NULL) + { + OrthancPluginFreeString(context_, str_); + } + } + + const char* GetValue() const + { + return str_; + } +}; + + +class OrthancBytes : public NonCopyable +{ +private: + OrthancPluginMemoryBuffer buffer_; + +public: + OrthancBytes() + { + buffer_.data = NULL; + buffer_.size = 0; + } + + ~OrthancBytes() + { + OrthancPluginFreeMemoryBuffer(context_, &buffer_); + } + + OrthancPluginMemoryBuffer* GetMemoryBuffer() + { + return &buffer_; + } + + const void* GetData() const + { + return buffer_.data; + } + + size_t GetSize() const + { + return buffer_.size; + } +}; + + +class JavaGlobalReference : public NonCopyable +{ +private: + JavaVirtualMachine& jvm_; + jobject obj_; + +public: + JavaGlobalReference(JavaVirtualMachine& jvm, + jobject obj) : + jvm_(jvm), + obj_(NULL) + { + if (obj == NULL) + { + throw std::runtime_error("Null pointer"); + } + + JavaEnvironment env(jvm); + + obj_ = env.GetValue().NewGlobalRef(obj); + if (obj_ == NULL) + { + throw std::runtime_error("Cannot create global reference"); + } + } + + ~JavaGlobalReference() + { + assert(obj_ != NULL); + + try + { + JavaEnvironment env(jvm_); + env.GetValue().DeleteGlobalRef(obj_); + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + } + } + + jobject GetValue() + { + assert(obj_ != NULL); + return obj_; + } +}; + + +class LocalJavaObject : public NonCopyable +{ +private: + JNIEnv* env_; + jobject obj_; + +public: + LocalJavaObject(JavaEnvironment& env, + jobject obj, + bool objCanBeNull = false) : + env_(&env.GetValue()), + obj_(obj) + { + if (!objCanBeNull && obj == NULL) + { + throw std::runtime_error("Null pointer"); + } + } + + ~LocalJavaObject() + { + env_->DeleteLocalRef(obj_); + } + + jobject GetValue() + { + return obj_; + } + + static LocalJavaObject* CreateArrayOfStrings(JavaEnvironment& env, + const std::vector<std::string>& items) + { + LocalJavaObject emptyString(env, env.GetValue().NewStringUTF("")); + + jobjectArray obj = env.GetValue().NewObjectArray( + items.size(), env.GetValue().FindClass("java/lang/String"), + emptyString.GetValue()); + + if (obj == NULL) + { + throw std::runtime_error("Cannot create an array of Java strings"); + } + else + { + std::unique_ptr<LocalJavaObject> result(new LocalJavaObject(env, obj)); + + for (size_t i = 0; i < items.size(); i++) + { + LocalJavaObject item(env, env.GetValue().NewStringUTF(items[i].c_str())); + env.GetValue().SetObjectArrayElement(obj, i, item.GetValue()); + } + + return result.release(); + } + } + + static LocalJavaObject* CreateDictionary(JavaEnvironment& env, + const std::map<std::string, std::string>& items) + { + // NB: In JNI, there are no generics. All the templated arguments + // are taken as instances of the "Object" base class. + + jclass cls = env.FindClass("java/util/HashMap"); + jmethodID constructor = env.GetMethodID(cls, "<init>", "()V"); + jmethodID setter = env.GetMethodID(cls, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + jobject obj = env.GetValue().NewObject(cls, constructor); + + if (obj == NULL) + { + throw std::runtime_error("Cannot create a Java dictionary"); + } + else + { + std::unique_ptr<LocalJavaObject> result(new LocalJavaObject(env, obj)); + + for (std::map<std::string, std::string>::const_iterator it = items.begin(); it != items.end(); ++it) + { + LocalJavaObject key(env, env.GetValue().NewStringUTF(it->first.c_str())); + LocalJavaObject value(env, env.GetValue().NewStringUTF(it->second.c_str())); + LocalJavaObject previousValue(env, env.GetValue().CallObjectMethod(obj, setter, key.GetValue(), value.GetValue()), true); + env.CheckException(); + } + + return result.release(); + } + } +}; + + + +#include "NativeSDK.cpp" + + + +#define MAX_REST_CALLBACKS 10 + +class CallbacksConfiguration : public NonCopyable +{ +private: + Mutex mutex_; + std::list<JavaGlobalReference*> onChangeCallbacks_; + std::vector<JavaGlobalReference*> onRestRequestCallbacks_; + + static void DestructCallbacks(std::list<JavaGlobalReference*>& lst) + { + for (std::list<JavaGlobalReference*>::iterator it = lst.begin(); it != lst.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + } + + static void DestructCallbacks(std::vector<JavaGlobalReference*>& v) + { + for (size_t i = 0; i < v.size(); i++) + { + assert(v[i] != NULL); + delete v[i]; + } + } + + void CopyCallbacks(std::list<jobject>& target, + const std::list<JavaGlobalReference*>& lst) + { + Mutex::Locker locker(mutex_); + target.clear(); + + for (std::list<JavaGlobalReference*>::const_iterator it = lst.begin(); it != lst.end(); ++it) + { + assert(*it != NULL); + target.push_back((*it)->GetValue()); + } + } + + void AddCallback(std::list<JavaGlobalReference*>& lst, + JavaVirtualMachine& jvm, + jobject callback) + { + if (callback == NULL) + { + throw std::runtime_error("Null pointer"); + } + else + { + Mutex::Locker locker(mutex_); + lst.push_back(new JavaGlobalReference(jvm, callback)); + } + } + +public: + CallbacksConfiguration() + { + onRestRequestCallbacks_.reserve(MAX_REST_CALLBACKS); + } + + ~CallbacksConfiguration() + { + DestructCallbacks(onChangeCallbacks_); + DestructCallbacks(onRestRequestCallbacks_); + } + + void AddOnChangeCallback(JavaVirtualMachine& jvm, + jobject callback) + { + AddCallback(onChangeCallbacks_, jvm, callback); + } + + void GetOnChangeCallbacks(std::list<jobject>& target) + { + CopyCallbacks(target, onChangeCallbacks_); + } + + size_t AddOnRestRequestCallback(JavaVirtualMachine& jvm, + jobject callback) + { + if (callback == NULL) + { + throw std::runtime_error("Null pointer"); + } + else + { + Mutex::Locker locker(mutex_); + + if (onRestRequestCallbacks_.size() >= MAX_REST_CALLBACKS) + { + char buf[16]; + sprintf(buf, "%d", MAX_REST_CALLBACKS); + throw std::runtime_error("The Java plugin for Orthanc has been compiled for a maximum of " + + std::string(buf) + " REST callbacks"); + } + else + { + size_t result = onRestRequestCallbacks_.size(); + onRestRequestCallbacks_.push_back(new JavaGlobalReference(jvm, callback)); + return result; + } + } + } + + jobject GetOnRestCallback(size_t i) + { + Mutex::Locker locker(mutex_); + + if (i >= onRestRequestCallbacks_.size()) + { + throw std::runtime_error("Unknown REST callback"); + } + else + { + assert(onRestRequestCallbacks_[i] != NULL); + return onRestRequestCallbacks_[i]->GetValue(); + } + } +}; + +static std::unique_ptr<CallbacksConfiguration> callbacksConfiguration_; + + + + +template<size_t Index> +class RestCallbacksPool +{ +private: + RestCallbacksPool<Index - 1> next_; + + static OrthancPluginErrorCode Callback(OrthancPluginRestOutput* output, + const char* uri, + const OrthancPluginHttpRequest* request) + { + try + { + jobject callback = callbacksConfiguration_->GetOnRestCallback(MAX_REST_CALLBACKS - Index); + if (callback == NULL) + { + throw std::runtime_error("Missing callback"); + } + + std::vector<std::string> groups; + groups.resize(request->groupsCount); + for (uint32_t i = 0; i < request->groupsCount; i++) + { + groups[i].assign(request->groups[i]); + } + + std::map<std::string, std::string> headers; + for (uint32_t i = 0; i < request->headersCount; i++) + { + headers[request->headersKeys[i]] = request->headersValues[i]; + } + + std::map<std::string, std::string> getParameters; + for (uint32_t i = 0; i < request->getCount; i++) + { + getParameters[request->getKeys[i]] = request->getValues[i]; + } + + JavaEnvironment env(*java_); + + LocalJavaObject joutput(env, env.ConstructJavaWrapper("be/uclouvain/orthanc/RestOutput", output)); + LocalJavaObject jmethod(env, env.ConstructEnumValue("be/uclouvain/orthanc/HttpMethod", request->method)); + LocalJavaObject juri(env, env.GetValue().NewStringUTF(uri == NULL ? "" : uri)); + std::unique_ptr<LocalJavaObject> jgroups(LocalJavaObject::CreateArrayOfStrings(env, groups)); + std::unique_ptr<LocalJavaObject> jheaders(LocalJavaObject::CreateDictionary(env, headers)); + std::unique_ptr<LocalJavaObject> jgetParameters(LocalJavaObject::CreateDictionary(env, getParameters)); + LocalJavaObject jbody(env, env.ConstructByteArray(request->bodySize, request->body)); + + jmethodID call = env.GetMethodID( + env.GetObjectClass(callback), "call", + "(Lbe/uclouvain/orthanc/RestOutput;Lbe/uclouvain/orthanc/HttpMethod;Ljava/lang/String;" + "[Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;[B)V"); + + env.GetValue().CallVoidMethod(callback, call, joutput.GetValue(), jmethod.GetValue(), juri.GetValue(), + jgroups->GetValue(), jheaders->GetValue(), jgetParameters->GetValue(), jbody.GetValue()); + env.CheckException(); + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + return OrthancPluginErrorCode_Plugin; + } + catch (...) + { + OrthancPluginLogError(context_, "Caught native exception"); + return OrthancPluginErrorCode_Plugin; + } + } + +public: + OrthancPluginRestCallback GetCallback(size_t i) + { + if (i == 0) + { + return Callback; + } + else + { + return next_.GetCallback(i - 1); + } + } +}; + +template<> +class RestCallbacksPool<0> +{ +public: + OrthancPluginRestCallback& GetCallback(size_t i) + { + throw std::runtime_error("Out of tuple"); + } +}; + + +static RestCallbacksPool<MAX_REST_CALLBACKS> restCallbacksPool_; + + + +OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) +{ + try + { + std::list<jobject> callbacks; + callbacksConfiguration_->GetOnChangeCallbacks(callbacks); + + if (!callbacks.empty()) + { + JavaEnvironment env(*java_); + + LocalJavaObject c(env, env.ConstructEnumValue("be/uclouvain/orthanc/ChangeType", changeType)); + LocalJavaObject r(env, env.ConstructEnumValue("be/uclouvain/orthanc/ResourceType", resourceType)); + LocalJavaObject s(env, env.GetValue().NewStringUTF(resourceId == NULL ? "" : resourceId)); + + for (std::list<jobject>::const_iterator + callback = callbacks.begin(); callback != callbacks.end(); ++callback) + { + assert(*callback != NULL); + + jmethodID call = env.GetMethodID( + env.GetObjectClass(*callback), "call", + "(Lbe/uclouvain/orthanc/ChangeType;Lbe/uclouvain/orthanc/ResourceType;Ljava/lang/String;)V"); + + env.GetValue().CallVoidMethod(*callback, call, c.GetValue(), r.GetValue(), s.GetValue()); + env.CheckException(); + } + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + return OrthancPluginErrorCode_Plugin; + } + catch (...) + { + OrthancPluginLogError(context_, "Caught native exception"); + return OrthancPluginErrorCode_Plugin; + } +} + + +JNIEXPORT void RegisterOnChangeCallback(JNIEnv* env, jobject sdkObject, jobject callback) +{ + try + { + callbacksConfiguration_->AddOnChangeCallback(*java_, callback); + } + catch (std::runtime_error& e) + { + JavaEnvironment::ThrowException(env, e.what()); + } + catch (...) + { + JavaEnvironment::ThrowException(env, OrthancPluginErrorCode_Plugin); + } +} + + +JNIEXPORT void RegisterOnRestRequestCallback(JNIEnv* env, jobject sdkObject, jstring regex, jobject callback) +{ + try + { + JavaString cregex(env, regex); + size_t index = callbacksConfiguration_->AddOnRestRequestCallback(*java_, callback); + OrthancPluginRegisterRestCallbackNoLock(context_, cregex.GetValue(), restCallbacksPool_.GetCallback(index)); + } + catch (std::runtime_error& e) + { + JavaEnvironment::ThrowException(env, e.what()); + } + catch (...) + { + JavaEnvironment::ThrowException(env, OrthancPluginErrorCode_Plugin); + } +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + context_ = context; + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context, info); + return -1; + } + + try + { + { + // Sanity check to ensure that the compiler has created different callback functions + std::set<intptr_t> c; + for (unsigned int i = 0; i < MAX_REST_CALLBACKS; i++) + { + c.insert(reinterpret_cast<intptr_t>(restCallbacksPool_.GetCallback(i))); + } + + if (c.size() != MAX_REST_CALLBACKS) + { + throw std::runtime_error("The Java plugin has not been properly compiled"); + } + } + + java_.reset(new JavaVirtualMachine("TODO")); + + callbacksConfiguration_.reset(new CallbacksConfiguration); + OrthancPluginRegisterOnChangeCallback(context_, OnChangeCallback); + + JavaEnvironment env(*java_); + + { + std::vector<JNINativeMethod> methods; + JNI_LoadNatives(methods); + env.RegisterNatives("be/uclouvain/orthanc/NativeSDK", methods); + } + + { + std::vector<JNINativeMethod> methods; + methods.push_back((JNINativeMethod) { + const_cast<char*>("register"), + const_cast<char*>("(Lbe/uclouvain/orthanc/Callbacks$OnChange;)V"), + (void*) RegisterOnChangeCallback }); + methods.push_back((JNINativeMethod) { + const_cast<char*>("register"), + const_cast<char*>("(Ljava/lang/String;Lbe/uclouvain/orthanc/Callbacks$OnRestRequest;)V"), + (void*) RegisterOnRestRequestCallback }); + env.RegisterNatives("be/uclouvain/orthanc/Callbacks", methods); + } + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context, e.what()); + return -1; + } + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + if (java_.get() != NULL) + { + callbacksConfiguration_.reset(NULL); + + try + { + JavaEnvironment env(*java_); + env.RunGarbageCollector(); + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + } + + java_.reset(NULL); + } + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "java"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return PLUGIN_VERSION; + } +}