| /** |
| * |
| * 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. |
| */ |
| #ifndef EXTENSIONS_NARCLASSLOADER_H |
| #define EXTENSIONS_NARCLASSLOADER_H |
| |
| #include "JavaServicer.h" |
| #include "JniBundle.h" |
| #include "../JavaException.h" |
| #include <string> |
| #include <vector> |
| #include <sstream> |
| #include <iterator> |
| #include <algorithm> |
| #include <jni.h> |
| |
| namespace org { |
| namespace apache { |
| namespace nifi { |
| namespace minifi { |
| namespace jni { |
| |
| class NarClassLoader { |
| |
| public: |
| |
| NarClassLoader(std::shared_ptr<minifi::jni::JavaServicer> servicer, JavaClass &clazz, const std::string &dir_name, const std::string &scratch_nar_dir, const std::string &docs_dir) |
| : java_servicer_(servicer) { |
| class_ref_ = clazz; |
| auto env = java_servicer_->attach(); |
| class_loader_ = class_ref_.newInstance(env); |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "initializeNarDirectory", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V"); |
| if (mthd == nullptr) { |
| throw std::runtime_error("Could not find method to construct JniClassLoader"); |
| } else { |
| auto dirNameStr = env->NewStringUTF(dir_name.c_str()); |
| auto narWriteBaseStr = env->NewStringUTF(scratch_nar_dir.c_str()); |
| auto docsDirStr = env->NewStringUTF(docs_dir.c_str()); |
| env->CallVoidMethod(class_loader_, mthd, dirNameStr, narWriteBaseStr, docsDirStr, servicer->getClassLoader()); |
| ThrowIf(env); |
| } |
| |
| // we should now have |
| |
| getBundles(); |
| } |
| |
| ~NarClassLoader() { |
| java_servicer_->attach()->DeleteGlobalRef(class_loader_); |
| } |
| |
| jclass getClass(const std::string &requested_name) { |
| auto env = java_servicer_->attach(); |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "getClass", "(Ljava/lang/String;)Ljava/lang/Class;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| |
| auto clazz_name = env->NewStringUTF(requested_name.c_str()); |
| |
| auto job = env->CallObjectMethod(class_loader_, mthd, clazz_name); |
| |
| ThrowIf(env); |
| |
| return (jclass) job; |
| } |
| |
| std::pair<std::string, std::string> getAnnotation(const std::string &requested_name, const std::string &method_name) { |
| auto env = java_servicer_->attach(); |
| std::string methodName, signature; |
| { |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "getMethod", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| |
| auto clazz_name = env->NewStringUTF(requested_name.c_str()); |
| auto annotation_name = env->NewStringUTF(method_name.c_str()); |
| |
| jstring obj = (jstring) env->CallObjectMethod(class_loader_, mthd, clazz_name, annotation_name); |
| |
| ThrowIf(env); |
| |
| if (obj) { |
| methodName = JniStringToUTF(env, obj); |
| } |
| } |
| { |
| |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "getSignature", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| |
| auto clazz_name = env->NewStringUTF(requested_name.c_str()); |
| auto annotation_name = env->NewStringUTF(method_name.c_str()); |
| |
| jstring obj = (jstring) env->CallObjectMethod(class_loader_, mthd, clazz_name, annotation_name); |
| |
| ThrowIf(env); |
| |
| if (obj) { |
| signature = JniStringToUTF(env, obj); |
| } |
| } |
| |
| return std::make_pair(methodName, signature); |
| } |
| |
| std::map<std::string, std::string> getAnnotations(const std::string &requested_name, const std::string &method_name) { |
| auto env = java_servicer_->attach(); |
| std::vector<std::string> method_names; |
| std::map<std::string, std::string> methods_with_signatures; |
| { |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "getMethods", "(Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| |
| auto clazz_name = env->NewStringUTF(requested_name.c_str()); |
| auto annotation_name = env->NewStringUTF(method_name.c_str()); |
| |
| jobject jList = env->CallObjectMethod(class_loader_, mthd, clazz_name, annotation_name); |
| ThrowIf(env); |
| jclass cList = env->FindClass("java/util/List"); |
| jmethodID mSize = env->GetMethodID(cList, "size", "()I"); |
| jmethodID mGet = env->GetMethodID(cList, "get", "(I)Ljava/lang/Object;"); |
| |
| // get the size of the list |
| jint size = env->CallIntMethod(jList, mSize); |
| ThrowIf(env); |
| // walk through and fill the vector |
| for (jint i = 0; i < size; i++) { |
| jstring strObj = (jstring) env->CallObjectMethod(jList, mGet, i); |
| ThrowIf(env); |
| method_names.push_back(JniStringToUTF(env, strObj)); |
| } |
| } |
| for (const auto &method_name_str : method_names) { |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "getMethodSignature", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| |
| auto clazz_name = env->NewStringUTF(requested_name.c_str()); |
| auto jstring_method_name = env->NewStringUTF(method_name_str.c_str()); |
| auto annotation_name = env->NewStringUTF(method_name.c_str()); |
| |
| jstring obj = (jstring) env->CallObjectMethod(class_loader_, mthd, clazz_name, jstring_method_name, annotation_name); |
| |
| ThrowIf(env); |
| |
| if (obj) { |
| auto signature = JniStringToUTF(env, obj); |
| methods_with_signatures[method_name_str] = signature; |
| } |
| } |
| |
| return methods_with_signatures; |
| } |
| /** |
| * Call empty constructor |
| */ |
| JNIEXPORT |
| jobject newInstance(const std::string &requested_name) { |
| auto env = java_servicer_->attach(); |
| |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "createObject", "(Ljava/lang/String;)Ljava/lang/Object;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| |
| auto clazz_name = env->NewStringUTF(requested_name.c_str()); |
| |
| auto newref = env->CallObjectMethod(class_loader_, mthd, clazz_name); |
| |
| ThrowIf(env); |
| |
| jobject obj = env->NewGlobalRef(newref); |
| |
| ThrowIf(env); |
| |
| return obj; |
| } |
| |
| private: |
| |
| /** |
| * Call empty constructor |
| */ |
| jobject getBundles() { |
| auto env = java_servicer_->attach(); |
| |
| jmethodID mthd = env->GetMethodID(class_ref_.getReference(), "getBundles", "()Ljava/util/List;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| |
| jobject obj = env->CallObjectMethod(class_loader_, mthd); |
| |
| ThrowIf(env); |
| |
| auto list_class = env->FindClass("java/util/ArrayList"); |
| |
| ThrowIf(env); |
| |
| size_t size = getSize(list_class, env, obj); |
| |
| for (int i = 0; i < size; i++) { |
| |
| JniBundle bundle = getBundle(list_class, env, obj, i); |
| for (const auto &cd : bundle.getDescriptions()) { |
| |
| auto lastOfIdx = cd.class_name_.find_last_of("."); |
| if (lastOfIdx != std::string::npos) { |
| lastOfIdx++; // if a value is found, increment to move beyond the . |
| int nameLength = cd.class_name_.length() - lastOfIdx; |
| const auto processorName = cd.class_name_.substr(lastOfIdx, nameLength); |
| if (core::ClassLoader::getDefaultClassLoader().getGroupForClass(processorName).empty()) { |
| minifi::ExternalBuildDescription::addExternalComponent(bundle.getDetails(), cd); |
| } |
| } |
| } |
| } |
| |
| return obj; |
| } |
| |
| size_t getSize(jclass list_class, JNIEnv *env, jobject list) { |
| jmethodID mthd = env->GetMethodID(list_class, "size", "()I"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| jint obj = (jint) env->CallIntMethod(list, mthd); |
| minifi::jni::ThrowIf(env); |
| return obj; |
| } |
| |
| JniBundle getBundle(jclass list_class, JNIEnv *env, jobject list, int index) { |
| jmethodID mthd = env->GetMethodID(list_class, "get", "(I)Ljava/lang/Object;"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| auto bundle = env->CallObjectMethod(list, mthd, index); |
| ThrowIf(env); |
| if (bundle != nullptr) { |
| auto jni_bundle_clazz = getClass("org.apache.nifi.processor.JniBundle"); |
| struct BundleDetails details = getCoordinateDetails(env, jni_bundle_clazz, bundle); |
| std::vector<ClassDescription> descriptions = getDescriptions(env, jni_bundle_clazz, bundle); |
| |
| JniBundle newBundle(details); |
| for (const auto &cd : descriptions) { |
| newBundle.addDescription(cd); |
| } |
| |
| return newBundle; |
| } |
| // assuming we have the bundle, we need to get the coordinate. |
| |
| return JniBundle(); |
| |
| } |
| |
| std::vector<ClassDescription> getDescriptions(JNIEnv *env, jclass jni_bundle_clazz, jobject bundle) { |
| |
| std::vector<ClassDescription> descriptions; |
| auto jni_component_clazz = getClass("org.apache.nifi.processor.JniComponent"); |
| |
| auto list_class = env->FindClass("java/util/ArrayList"); |
| |
| if (nullptr == jni_component_clazz || nullptr == jni_bundle_clazz) { |
| return descriptions; |
| } |
| auto components = getComponents(jni_bundle_clazz, env, bundle); |
| |
| size_t size = getSize(list_class, env, components); |
| |
| for (int i = 0; i < size; i++) { |
| descriptions.push_back(getClassDescription(list_class, env, jni_component_clazz, components, i)); |
| } |
| |
| return descriptions; |
| } |
| |
| ClassDescription getClassDescription(jclass list_class, JNIEnv *env, jclass jni_component_clazz, jobject list, int index) { |
| jmethodID mthd = env->GetMethodID(list_class, "get", "(I)Ljava/lang/Object;"); |
| auto property_descriptor_clazz = getClass("org.apache.nifi.components.PropertyDescriptor"); |
| if (mthd == nullptr) { |
| ThrowIf(env); |
| } |
| auto component = env->CallObjectMethod(list, mthd, index); |
| minifi::jni::ThrowIf(env); |
| if (component != nullptr) { |
| |
| auto type = getStringMethod("getType", jni_component_clazz, env, component); |
| auto isControllerService = getBoolmethod("isControllerService", jni_component_clazz, env, component); |
| ClassDescription description(type); |
| { |
| jmethodID getDescriptorMethod = env->GetMethodID(jni_component_clazz, "getDescriptors", "()Ljava/util/List;"); |
| |
| jobject descriptors = env->CallObjectMethod(component, getDescriptorMethod); |
| |
| ThrowIf(env); |
| |
| if (descriptors) { |
| size_t size = getSize(list_class, env, descriptors); |
| |
| // iterate through each property descriptor |
| for (int i = 0; i < size; i++) { |
| auto propertyDescriptorObj = env->CallObjectMethod(descriptors, mthd, i); |
| minifi::jni::ThrowIf(env); |
| if (propertyDescriptorObj != nullptr) { |
| auto propName = getStringMethod("getName", property_descriptor_clazz, env, propertyDescriptorObj); |
| auto propDesc = getStringMethod("getDescription", property_descriptor_clazz, env, propertyDescriptorObj); |
| auto defaultValue = getStringMethod("getDefaultValue", property_descriptor_clazz, env, propertyDescriptorObj); |
| |
| auto builder = core::PropertyBuilder::createProperty(propName)->withDescription(propDesc); |
| if (!defaultValue.empty()) { |
| builder->withDefaultValue(defaultValue); |
| } |
| |
| builder = builder->isRequired(getBoolmethod("isRequired", property_descriptor_clazz, env, propertyDescriptorObj)); |
| core::Property prop(builder->build()); |
| description.class_properties_.insert(std::make_pair(prop.getName(), prop)); |
| |
| } |
| } |
| } |
| } |
| description.is_controller_service_ = isControllerService; |
| jmethodID getRelationshipsMethod = env->GetMethodID(jni_component_clazz, "getRelationships", "()Ljava/util/List;"); |
| ThrowIf(env); |
| jobject relationships = env->CallObjectMethod(component, getRelationshipsMethod); |
| ThrowIf(env); |
| if (relationships) { |
| size_t size = getSize(list_class, env, relationships); |
| |
| // iterate through each property descriptor |
| for (int i = 0; i < size; i++) { |
| auto propertyDescriptorObj = env->CallObjectMethod(relationships, mthd, i); |
| minifi::jni::ThrowIf(env); |
| if (propertyDescriptorObj != nullptr) { |
| auto relName = getStringMethod("getName", property_descriptor_clazz, env, propertyDescriptorObj); |
| auto relDesc = getStringMethod("getDescription", property_descriptor_clazz, env, propertyDescriptorObj); |
| |
| core::Relationship relationship(relName, relDesc); |
| |
| description.class_relationships_.push_back(relationship); |
| |
| } |
| } |
| } |
| |
| auto classDescription = getStringMethod("getDescription", jni_component_clazz, env, component); |
| description.dynamic_relationships_ = getBoolmethod("getDynamicRelationshipsSupported", jni_component_clazz, env, component); |
| description.dynamic_properties_ = getBoolmethod("getDynamicPropertiesSupported", jni_component_clazz, env, component); |
| |
| AgentDocs::putDescription(type, classDescription); |
| |
| return description; |
| |
| } |
| // assuming we have the bundle, we need to get the coordinate. |
| |
| return ClassDescription("unknown"); |
| |
| } |
| |
| struct BundleDetails getCoordinateDetails(JNIEnv *env, jclass jni_bundle_clazz, jobject bundle) { |
| |
| auto bundle_details = getClass("org.apache.nifi.bundle.BundleDetails"); |
| auto bundle_coordinate = getClass("org.apache.nifi.bundle.BundleCoordinate"); |
| struct BundleDetails details; |
| |
| if (nullptr == bundle_details || nullptr == bundle_coordinate || nullptr == jni_bundle_clazz) { |
| return details; |
| } |
| |
| auto jdetails = getDetails(jni_bundle_clazz, env, bundle); |
| |
| if (nullptr != jdetails) { |
| |
| auto jcoordinate = getCoordinate(bundle_details, env, jdetails); |
| if (nullptr != jcoordinate) { |
| details.artifact = getArtifact(bundle_coordinate, env, jcoordinate); |
| details.group = getGroup(bundle_coordinate, env, jcoordinate); |
| details.version = getVersion(bundle_coordinate, env, jcoordinate); |
| } |
| |
| } |
| |
| return details; |
| } |
| |
| bool getBoolmethod(const std::string &methodName, jclass bundle_coordinate, JNIEnv *env, jobject coord) { |
| jmethodID getIdMethod = env->GetMethodID(bundle_coordinate, methodName.c_str(), "()Z"); |
| |
| if (getIdMethod == nullptr) { |
| ThrowIf(env); |
| } |
| auto res = (jboolean) env->CallBooleanMethod(coord, getIdMethod); |
| ThrowIf(env); |
| return res; |
| } |
| |
| std::string getStringMethod(const std::string &methodName, jclass bundle_coordinate, JNIEnv *env, jobject coord) { |
| jmethodID getIdMethod = env->GetMethodID(bundle_coordinate, methodName.c_str(), "()Ljava/lang/String;"); |
| |
| if (getIdMethod == nullptr) { |
| ThrowIf(env); |
| } |
| auto id = (jstring) env->CallObjectMethod(coord, getIdMethod); |
| ThrowIf(env); |
| if (id == nullptr) |
| return ""; |
| return JniStringToUTF(env, id); |
| } |
| |
| std::string getArtifact(jclass bundle_coordinate, JNIEnv *env, jobject coord) { |
| return getStringMethod("getId", bundle_coordinate, env, coord); |
| } |
| |
| std::string getGroup(jclass bundle_coordinate, JNIEnv *env, jobject coord) { |
| return getStringMethod("getGroup", bundle_coordinate, env, coord); |
| } |
| |
| std::string getVersion(jclass bundle_coordinate, JNIEnv *env, jobject coord) { |
| return getStringMethod("getVersion", bundle_coordinate, env, coord); |
| } |
| |
| jobject getCoordinate(jclass bundle_details, JNIEnv *env, jobject jdetail) { |
| jmethodID getCoordinateMethod = env->GetMethodID(bundle_details, "getCoordinate", "()Lorg/apache/nifi/bundle/BundleCoordinate;"); |
| if (getCoordinateMethod == nullptr) { |
| ThrowIf(env); |
| } |
| auto coordinate = env->CallObjectMethod(jdetail, getCoordinateMethod); |
| ThrowIf(env); |
| return coordinate; |
| |
| } |
| |
| jobject getDetails(jclass bundle_details, JNIEnv *env, jobject bundle) { |
| jmethodID getDetailsMethod = env->GetMethodID(bundle_details, "getDetails", "()Lorg/apache/nifi/bundle/BundleDetails;"); |
| if (getDetailsMethod == nullptr) { |
| ThrowIf(env); |
| } |
| auto details = env->CallObjectMethod(bundle, getDetailsMethod); |
| ThrowIf(env); |
| return details; |
| |
| } |
| |
| jobject getComponents(jclass bundle_details, JNIEnv *env, jobject bundle) { |
| jmethodID getDetailsMethod = env->GetMethodID(bundle_details, "getComponents", "()Ljava/util/List;"); |
| if (getDetailsMethod == nullptr) { |
| ThrowIf(env); |
| } |
| auto details = env->CallObjectMethod(bundle, getDetailsMethod); |
| ThrowIf(env); |
| return details; |
| |
| } |
| |
| std::shared_ptr<logging::Logger> logger_; |
| std::shared_ptr<minifi::jni::JavaServicer> java_servicer_; |
| JavaClass class_ref_; |
| jobject class_loader_; |
| }; |
| |
| } /* namespace jni */ |
| } /* namespace minifi */ |
| } /* namespace nifi */ |
| } /* namespace apache */ |
| } /* namespace org */ |
| |
| #endif /* EXTENSIONS_NARCLASSLOADER_H */ |