extensions/jni/jvm/JVMLoader.h (343 lines of code) (raw):

/** * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <jni.h> #include <memory> #include <utility> #include <string> #include <string_view> #include <map> #include <vector> #include <sstream> #include <iterator> #include <algorithm> #include "JavaClass.h" #include "JavaServicer.h" #include "../JavaException.h" #include "core/Core.h" #ifndef WIN32 #include <dlfcn.h> #endif #include "Core.h" namespace org::apache::nifi::minifi::jni { /** * Purpose and Justification: Provides a mapping function for jfields. * Note that jFieldIDs aren't local references, so we don't need to worry * about keeping them around. Thus this class provides us a caching mechanism. */ class FieldMapping { public: jfieldID getField(const std::string &clazz, const std::string &fnArg) { std::lock_guard<std::mutex> guard(mutex_); auto group = map_.find(clazz); if (group != map_.end()) { auto match = group->second.find(fnArg); if (match != group->second.end()) { return match->second; } } return nullptr; } void putField(const std::string &clazz, const std::string &fnArg, jfieldID field) { std::lock_guard<std::mutex> guard(mutex_); map_[clazz].insert(std::make_pair(fnArg, field)); } private: std::mutex mutex_; std::map<std::string, std::map<std::string, jfieldID>> map_; }; typedef jint (*registerNatives_t)(JNIEnv* env, jclass clazz); jfieldID getPtrField(JNIEnv *env, jobject obj); template<typename T> T *getPtr(JNIEnv *env, jobject obj); template<typename T> void setPtr(JNIEnv *env, jobject obj, T *t); /** * Purpose and Justification: Provides a singleton reference to a JVM. * * Since JVMs are singular in reference ( meaning that there cannot be more than one * at any given time ), we need to create a singleton access pattern. * */ class JVMLoader { public: bool initialized() { return initialized_; } /** * Attach the current thread * @return JNIEnv reference. */ JNIEnv *attach(const std::string& /*name*/ = "") { JNIEnv* jenv; jint ret = jvm_->GetEnv(reinterpret_cast<void**>(&jenv), JNI_VERSION_1_8); if (ret == JNI_EDETACHED) { ret = jvm_->AttachCurrentThread(reinterpret_cast<void**>(&jenv), nullptr); if (ret != JNI_OK || jenv == nullptr) { throw std::runtime_error("Could not find class"); } } return jenv; } void detach() { jvm_->DetachCurrentThread(); } /** * Returns a reference to an instantiated class loader * @return class loader. */ jobject getClassLoader() { return gClassLoader; } /** * Removes a class reference * @param name class name */ void remove_class(const std::string &name) { std::lock_guard<std::mutex> lock(internal_mutex_); auto finder = objects_.find(name); if (finder != objects_.end()) { auto oldClzz = finder->second; JavaClass clazz(oldClzz); attach(name)->DeleteGlobalRef(clazz.getReference()); } objects_.erase(name); } /** * Loads a class, creating a global reference to the jclass * @param class name. * @return JavaClass */ JavaClass load_class(const std::string &clazz_name, JNIEnv *lenv = nullptr) { // names should come with package/package/name, so we must normalize the name JNIEnv *env = lenv; // when we do the lookup here we will be using a boostrap CL so we'll need // to ensure class names are not in their canonical form. Since we want to // support both we will do the transition from . to / and later on from // / to . to normalize. Forcing all classes that enter this method // to be a certain way isn't very defensible. std::string name = clazz_name; name = utils::StringUtils::replaceAll(name, ".", "/"); if (env == nullptr) { env = attach(name); } std::lock_guard<std::mutex> lock(internal_mutex_); auto finder = objects_.find(name); if (finder != objects_.end()) { auto oldClzz = finder->second; JavaClass clazz(oldClzz); return clazz; } std::string modifiedName = name; modifiedName = utils::StringUtils::replaceAll(modifiedName, "/", "."); auto jstringclass = env->NewStringUTF(modifiedName.c_str()); auto preclass = env->CallObjectMethod(gClassLoader, gFindClassMethod, jstringclass); minifi::jni::ThrowIf(env); auto obj = (jclass) env->NewGlobalRef(preclass); minifi::jni::ThrowIf(env); auto clazzobj = static_cast<jclass>(obj); JavaClass clazz(name, clazzobj, env); objects_.insert(std::make_pair(name, clazz)); return clazz; } JavaClass getObjectClass(const std::string &name, jobject jobj) { auto env = attach(); auto jcls = (jclass) env->NewGlobalRef(env->GetObjectClass(jobj)); return JavaClass(name, jcls, env); } static JVMLoader *getInstance() { static JVMLoader jvm; return &jvm; } JNIEnv *getEnv() { return env_; } /** * Returns an instance to the JVMLoader * @param pathVector vector of paths * @param otherOptions jvm options. */ static JVMLoader *getInstance(const std::vector<std::filesystem::path> &pathVector, const std::vector<std::string> &otherOptions = std::vector<std::string>()) { JVMLoader *jvm = getInstance(); if (!jvm->initialized()) { std::stringstream str; std::vector<std::string> options; for (const auto &path : pathVector) { if (str.str().length() > 0) { #ifdef WIN32 str << ";" << path; #else str << ":" << path; #endif } else { str << path; } } options.insert(options.end(), otherOptions.begin(), otherOptions.end()); std::string classpath = "-Djava.class.path=" + str.str(); options.push_back(classpath); jvm->initialize(options); } return jvm; } template<typename T> void setReference(jobject obj, T *t) { setPtr(env_, obj, t); } template<typename T> void setReference(jobject obj, JNIEnv *env, T *t) { setPtr(env, obj, t); } template<typename T> T *getReference(JNIEnv *env, jobject obj) { return getPtr<T>(env, obj); } /** * Get the pointer field to the * This expects nativePtr to exist. I've always used nativePtr because I know others use it across * stack overflow. It's a common field, so the hope is that we can potentially access any class * in which the native pointer is stored in nativePtr. */ static jfieldID getPtrField(std::string_view className, JNIEnv *env, jobject obj) { static std::string fn = "nativePtr", args = "J", lookup = "nativePtrJ"; auto field = getClassMapping().getField(std::string(className), lookup); if (field != nullptr) { return field; } jclass c = env->GetObjectClass(obj); return env->GetFieldID(c, "nativePtr", "J"); } template<typename T> static T *getPtr(JNIEnv *env, jobject obj) { jlong handle = env->GetLongField(obj, getPtrField(minifi::core::className<T>(), env, obj)); return reinterpret_cast<T *>(handle); } template<typename T> static void setPtr(JNIEnv *env, jobject obj, T *t) { jlong handle = reinterpret_cast<jlong>(t); env->SetLongField(obj, getPtrField(minifi::core::className<T>(), env, obj), handle); } void setBaseServicer(std::shared_ptr<JavaServicer> servicer) { java_servicer_ = servicer; } std::shared_ptr<JavaServicer> getBaseServicer() const { std::lock_guard<std::mutex> lock(internal_mutex_); return java_servicer_; } template<typename T> static void putClassMapping(JNIEnv *env, JavaClass &clazz, const std::string &fieldStr, const std::string &arg) { auto classref = clazz.getReference(); auto name = std::string{minifi::core::className<T>()}; auto field = env->GetFieldID(classref, fieldStr.c_str(), arg.c_str()); auto fieldName = fieldStr + arg; getClassMapping().putField(name, fieldName, field); } protected: static FieldMapping &getClassMapping() { static FieldMapping map; return map; } mutable std::mutex internal_mutex_; #ifdef WIN32 // base_object doesn't have a handle std::map< HMODULE, std::string > resource_mapping_; std::string error_str_; std::string current_error_; void store_error() { auto error = GetLastError(); if (error == 0) { error_str_ = ""; return; } LPSTR messageBuffer = nullptr; size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); current_error_ = std::string(messageBuffer, size); // Free the buffer. LocalFree(messageBuffer); } void *dlsym(void *handle, const char *name) { FARPROC symbol; HMODULE hModule; symbol = GetProcAddress((HMODULE)handle, name); if (symbol == nullptr) { store_error(); for (auto hndl : resource_mapping_) { symbol = GetProcAddress((HMODULE)hndl.first, name); if (symbol != nullptr) { break; } } } #ifdef _MSC_VER #pragma warning(suppress: 4054) #endif return reinterpret_cast<void*>(symbol); } const char *dlerror(void) { std::lock_guard<std::mutex> lock(internal_mutex_); error_str_ = current_error_; current_error_ = ""; return error_str_.c_str(); } void *dlopen(const char *file, int mode) { std::lock_guard<std::mutex> lock(internal_mutex_); HMODULE object; char * current_error = NULL; uint32_t uMode = SetErrorMode(SEM_FAILCRITICALERRORS); if (nullptr == file) { HMODULE allModules[1024]; HANDLE current_process_id = GetCurrentProcess(); DWORD cbNeeded; object = GetModuleHandle(NULL); if (!object) store_error(); if (EnumProcessModules(current_process_id, allModules, sizeof(allModules), &cbNeeded) != 0) { for (uint32_t i = 0; i < cbNeeded / sizeof(HMODULE); i++) { TCHAR szModName[MAX_PATH]; // Get the full path to the module's file. resource_mapping_.insert(std::make_pair(allModules[i], "minifi-system")); } } } else { char lpFileName[MAX_PATH]; int i; for (i = 0; i < sizeof(lpFileName) - 1; i++) { if (!file[i]) break; else if (file[i] == '/') lpFileName[i] = '\\'; else lpFileName[i] = file[i]; } lpFileName[i] = '\0'; object = LoadLibraryEx(lpFileName, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); if (!object) store_error(); else if ((mode & RTLD_GLOBAL)) resource_mapping_.insert(std::make_pair(object, lpFileName)); } /* Return to previous state of the error-mode bit flags. */ SetErrorMode(uMode); return reinterpret_cast<void *>(object); } int dlclose(void *handle) { std::lock_guard<std::mutex> lock(internal_mutex_); HMODULE object = (HMODULE)handle; BOOL ret; current_error_ = ""; ret = FreeLibrary(object); resource_mapping_.erase(object); ret = !ret; return static_cast<int>(ret); } #endif inline jclass find_class_global(JNIEnv* env, const char *name) { jclass c = env->FindClass(name); jclass c_global = (jclass) env->NewGlobalRef(c); if (!c) { std::stringstream ss; ss << "Could not find " << name; throw std::runtime_error(ss.str()); } return c_global; } void initialize(const std::vector<std::string> &opts) { string_options_ = opts; java_options_ = new JavaVMOption[opts.size()]; int i = 0; for (const auto &opt : string_options_) { java_options_[i++].optionString = const_cast<char*>(opt.c_str()); } JavaVMInitArgs vm_args; // rely on 1.8 and above vm_args.version = JNI_VERSION_1_8; vm_args.nOptions = opts.size(); vm_args.options = java_options_; // if we see an unrecognized option fail. vm_args.ignoreUnrecognized = JNI_FALSE; // load and initialize a Java VM, return a JNI interface // pointer in env JNI_CreateJavaVM(&jvm_, reinterpret_cast<void **>(&env_), &vm_args); // we're actually using a known class to locate the class loader and provide it // to referentially perform lookups. auto randomClass = find_class_global(env_, "org/apache/nifi/processor/ProcessContext"); jclass classClass = env_->GetObjectClass(randomClass); auto classLoaderClass = find_class_global(env_, "java/lang/ClassLoader"); auto getClassLoaderMethod = env_->GetMethodID(classClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); auto refclazz = env_->CallObjectMethod(randomClass, getClassLoaderMethod); minifi::jni::ThrowIf(env_); gClassLoader = env_->NewGlobalRef(refclazz); gFindClassMethod = env_->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); minifi::jni::ThrowIf(env_); initialized_ = true; } private: std::atomic<bool> initialized_; std::map<std::string, JavaClass> objects_; std::vector<std::string> string_options_; JavaVMOption* java_options_; JavaVM *jvm_; JNIEnv *env_; jobject gClassLoader; jmethodID gFindClassMethod; std::shared_ptr<JavaServicer> java_servicer_; JVMLoader() : java_options_(nullptr), jvm_(nullptr), env_(nullptr), gClassLoader(nullptr), gFindClassMethod(nullptr), java_servicer_(nullptr) { initialized_ = false; } ~JVMLoader() { if (java_options_) { delete[] java_options_; } } }; } // namespace org::apache::nifi::minifi::jni