blob: 2260abaa5d534e096753e3bcb0b74b17ed953e7b [file] [log] [blame]
/**
*
* 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 */