Mercurial > hg > orthanc-java
view Plugin/Plugin.cpp @ 17:443d5b7d733e
sync
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 05 Jan 2024 16:29:13 +0100 |
parents | 26c08ff926a3 |
children | 1c407ba1d311 |
line wrap: on
line source
/** * 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 "JavaLocalObject.h" #include "JavaGlobalReference.h" #include "OrthancBytes.h" #include "OrthancString.h" #include "JavaBytes.h" #include "JavaString.h" #include "JavaEnvironment.h" #include "JavaVirtualMachine.h" #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" OrthancPluginContext* context_ = NULL; static std::unique_ptr<JavaVirtualMachine> java_; // This function is implemented in the "NativeSDK.cpp" auto-generated file extern void JNI_LoadNatives(std::vector<JNINativeMethod>& methods); #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_); JavaLocalObject joutput(env, env.ConstructJavaWrapper("be/uclouvain/orthanc/RestOutput", output)); JavaLocalObject jmethod(env, env.ConstructEnumValue("be/uclouvain/orthanc/HttpMethod", request->method)); JavaLocalObject juri(env, env.GetValue().NewStringUTF(uri == NULL ? "" : uri)); std::unique_ptr<JavaLocalObject> jgroups(JavaLocalObject::CreateArrayOfStrings(env, groups)); std::unique_ptr<JavaLocalObject> jheaders(JavaLocalObject::CreateDictionary(env, headers)); std::unique_ptr<JavaLocalObject> jgetParameters(JavaLocalObject::CreateDictionary(env, getParameters)); JavaLocalObject 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_); JavaLocalObject c(env, env.ConstructEnumValue("be/uclouvain/orthanc/ChangeType", changeType)); JavaLocalObject r(env, env.ConstructEnumValue("be/uclouvain/orthanc/ResourceType", resourceType)); JavaLocalObject 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::ThrowOrthancException(env, e.what()); } catch (...) { JavaEnvironment::ThrowOrthancException(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::ThrowOrthancException(env, e.what()); } catch (...) { JavaEnvironment::ThrowOrthancException(env, OrthancPluginErrorCode_Plugin); } } static void ParseJson(Json::Value& target, const std::string& source) { Json::CharReaderBuilder builder; builder.settings_["collectComments"] = false; const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); assert(reader.get() != NULL); JSONCPP_STRING err; if (!reader->parse(source.c_str(), source.c_str() + source.size(), &target, &err)) { throw std::runtime_error("Cannot parse JSON: " + err); } } static bool HasOption(const Json::Value& json, const std::string& key, Json::ValueType type, bool isMandatory) { if (!json.isMember(key)) { if (isMandatory) { throw std::runtime_error("Missing configuration option for the Java plugin: \"" + key + "\""); } else { return false; } } else if (json[key].type() == type) { return true; } else { throw std::runtime_error("The configuration option \"" + key + "\" for the Java plugin has not the proper type"); } } static std::string GetMandatoryString(const Json::Value& json, const std::string& key) { HasOption(json, key, Json::stringValue, true); assert(json.isMember(key) && json[key].type() == Json::stringValue); return json[key].asString(); } 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; } OrthancPluginSetDescription(context, "Java plugin for Orthanc"); 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"); } } Json::Value globalConfiguration; { OrthancString tmp(OrthancPluginGetConfiguration(context)); ParseJson(globalConfiguration, tmp.GetValue()); } static const std::string KEY_JAVA = "Java"; static const std::string KEY_ENABLED = "Enabled"; static const std::string KEY_CLASSPATH = "Classpath"; static const std::string KEY_INITIALIZATION_CLASS = "InitializationClass"; if (!HasOption(globalConfiguration, KEY_JAVA, Json::objectValue, false)) { OrthancPluginLogInfo(context, "Java plugin is disabled"); return 0; } Json::Value javaConfiguration = globalConfiguration[KEY_JAVA]; assert(javaConfiguration.isObject()); if (HasOption(javaConfiguration, KEY_ENABLED, Json::booleanValue, false) && !javaConfiguration[KEY_ENABLED].asBool()) { OrthancPluginLogInfo(context, "Java plugin is disabled"); return 0; } java_.reset(new JavaVirtualMachine(GetMandatoryString(javaConfiguration, KEY_CLASSPATH))); 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); } if (HasOption(javaConfiguration, KEY_INITIALIZATION_CLASS, Json::stringValue, false)) { env.FindClass(javaConfiguration[KEY_INITIALIZATION_CLASS].asString()); } } 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; } }