view Plugin/Plugin.cpp @ 2:76114d03e318

fix legacy
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 18 Oct 2023 18:54:12 +0200
parents 3ecef5782f2c
children 9032ffb3a7d5
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 <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;
  }
}