GitHub Viewer
#include "java.h"
#include
#ifdef WIN32
#else
#include
#endif
#include "javaObject.h"
#include "javaScope.h"
#include "methodCallBaton.h"
#include "node_NodeDynamicProxyClass.h"
#include
#include
#include
#define DYNAMIC_PROXY_JS_ERROR -4
long v8ThreadId;
/*static*/ Nan::Persistent Java::s_ct;
/*static*/ std::string Java::s_nativeBindingLocation;
void my_sleep(int dur) {
#ifdef WIN32
Sleep(dur);
#else
usleep(dur);
#endif
}
long my_getThreadId() {
#ifdef WIN32
return (long)GetCurrentThreadId();
#else
return (long)pthread_self();
#endif
}
/*static*/ void Java::Init(v8::Handle target) {
Nan::HandleScope scope;
v8ThreadId = my_getThreadId();
v8::Local t = Nan::New(New);
s_ct.Reset(t);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(Nan::New("Java").ToLocalChecked());
Nan::SetPrototypeMethod(t, "getClassLoader", getClassLoader);
Nan::SetPrototypeMethod(t, "newInstance", newInstance);
Nan::SetPrototypeMethod(t, "newInstanceSync", newInstanceSync);
Nan::SetPrototypeMethod(t, "newProxy", newProxy);
Nan::SetPrototypeMethod(t, "callStaticMethod", callStaticMethod);
Nan::SetPrototypeMethod(t, "callStaticMethodSync", callStaticMethodSync);
Nan::SetPrototypeMethod(t, "callMethod", callMethod);
Nan::SetPrototypeMethod(t, "callMethodSync", callMethodSync);
Nan::SetPrototypeMethod(t, "findClassSync", findClassSync);
Nan::SetPrototypeMethod(t, "newArray", newArray);
Nan::SetPrototypeMethod(t, "newByte", newByte);
Nan::SetPrototypeMethod(t, "newShort", newShort);
Nan::SetPrototypeMethod(t, "newLong", newLong);
Nan::SetPrototypeMethod(t, "newChar", newChar);
Nan::SetPrototypeMethod(t, "newFloat", newFloat);
Nan::SetPrototypeMethod(t, "newDouble", newDouble);
Nan::SetPrototypeMethod(t, "getStaticFieldValue", getStaticFieldValue);
Nan::SetPrototypeMethod(t, "setStaticFieldValue", setStaticFieldValue);
Nan::SetPrototypeMethod(t, "instanceOf", instanceOf);
target->Set(Nan::New("Java").ToLocalChecked(), t->GetFunction());
JavaProxyObject::init();
}
NAN_METHOD(Java::New) {
Nan::HandleScope scope;
Java *self = new Java();
self->Wrap(info.This());
self->handle()->Set(Nan::New("classpath").ToLocalChecked(), Nan::New());
self->handle()->Set(Nan::New("options").ToLocalChecked(), Nan::New());
self->handle()->Set(Nan::New("nativeBindingLocation").ToLocalChecked(), Nan::New("Not Set").ToLocalChecked());
self->handle()->Set(Nan::New("asyncOptions").ToLocalChecked(), Nan::Null());
info.GetReturnValue().Set(info.This());
}
Java::Java() {
this->m_jvm = NULL;
this->m_env = NULL;
m_SyncSuffix = "Sync";
m_AsyncSuffix = "";
doSync = true;
doAsync = true;
doPromise = false;
}
Java::~Java() {
this->destroyJVM(&this->m_jvm, &this->m_env);
}
v8::Local Java::ensureJvm() {
if(!m_jvm) {
v8::Local result = createJVM(&this->m_jvm, &this->m_env);
assert(result->IsNull());
return result;
}
return Nan::Null();
}
void Java::configureAsync(v8::Local& asyncOptions) {
v8::Local asyncOptionsObj = asyncOptions.As();
m_SyncSuffix = "invalid";
m_AsyncSuffix = "invalid";
m_PromiseSuffix = "invalid";
doSync = false;
doAsync = false;
doPromise = false;
v8::Local suffixValue = asyncOptionsObj->Get(Nan::New("syncSuffix").ToLocalChecked());
if (suffixValue->IsString()) {
v8::Local suffix = suffixValue->ToString();
v8::String::Utf8Value utf8(suffix);
m_SyncSuffix.assign(*utf8);
doSync = true;
}
suffixValue = asyncOptionsObj->Get(Nan::New("asyncSuffix").ToLocalChecked());
if (suffixValue->IsString()) {
v8::Local suffix = suffixValue->ToString();
v8::String::Utf8Value utf8(suffix);
m_AsyncSuffix.assign(*utf8);
doAsync = true;
}
suffixValue = asyncOptionsObj->Get(Nan::New("promiseSuffix").ToLocalChecked());
if (suffixValue->IsString()) {
v8::Local suffix = suffixValue->ToString();
v8::String::Utf8Value utf8(suffix);
m_PromiseSuffix.assign(*utf8);
v8::Local promisify = asyncOptionsObj->Get(Nan::New("promisify").ToLocalChecked());
if (!promisify->IsFunction()) {
fprintf(stderr, "asyncOptions.promisify must be a function");
assert(promisify->IsFunction());
}
doPromise = true;
}
if (doSync && doAsync) {
assert(m_SyncSuffix != m_AsyncSuffix);
}
if (doSync && doPromise) {
assert(m_SyncSuffix != m_PromiseSuffix);
}
if (doAsync && doPromise) {
assert(m_AsyncSuffix != m_PromiseSuffix);
}
m_asyncOptions.Reset(asyncOptionsObj);
}
v8::Local Java::createJVM(JavaVM** jvm, JNIEnv** env) {
v8::Local asyncOptions = this->handle()->Get(Nan::New("asyncOptions").ToLocalChecked());
if (asyncOptions->IsObject()) {
configureAsync(asyncOptions);
}
// setup classpath
std::ostringstream classPath;
classPath handle()->Get(Nan::New("classpath").ToLocalChecked());
if(!classPathValue->IsArray()) {
return Nan::TypeError("Classpath must be an array");
}
v8::Local classPathArrayTemp = v8::Local::Cast(classPathValue);
m_classPathArray.Reset(classPathArrayTemp);
for(uint32_t i=0; iLength(); i++) {
if(i != 0) {
#ifdef WIN32
classPath IsString()) {
return Nan::TypeError("Classpath must only contain strings");
}
v8::Local arrayItem = arrayItemValue->ToString();
v8::String::Utf8Value arrayItemStr(arrayItem);
classPath handle()->Get(Nan::New("nativeBindingLocation").ToLocalChecked());
v8::String::Utf8Value nativeBindingLocationStr(v8NativeBindingLocation);
s_nativeBindingLocation = *nativeBindingLocationStr;
// get other options
v8::Local optionsValue = this->handle()->Get(Nan::New("options").ToLocalChecked());
if(!optionsValue->IsArray()) {
return Nan::TypeError("options must be an array");
}
v8::Local optionsArrayTemp = v8::Local::Cast(optionsValue);
m_optionsArray.Reset(optionsArrayTemp);
// create vm options
int vmOptionsCount = optionsArrayTemp->Length() + 1;
JavaVMOption* vmOptions = new JavaVMOption[vmOptionsCount];
//printf("classPath: %s\n", classPath.str().c_str());
vmOptions[0].optionString = strdup(classPath.str().c_str());
for(uint32_t i=0; iLength(); i++) {
v8::Local arrayItemValue = optionsArrayTemp->Get(i);
if(!arrayItemValue->IsString()) {
delete[] vmOptions;
return Nan::TypeError("options must only contain strings");
}
v8::Local arrayItem = arrayItemValue->ToString();
v8::String::Utf8Value arrayItemStr(arrayItem);
vmOptions[i+1].optionString = strdup(*arrayItemStr);
}
JavaVMInitArgs args;
// The JNI invocation is documented to include a function JNI_GetDefaultJavaVMInitArgs that
// was formerly called here. But the documentation from Oracle is confusing/contradictory.
// 1) It claims that the caller must set args.version before calling JNI_GetDefaultJavaVMInitArgs, which
// we did not do.
// 2) The sample code provide at the top of the doc doesn't even call JNI_GetDefaultJavaVMInitArgs.
// 3) The Oracle documentation for Java 6 through Java 8 all contain a comment "Note that in the JDK/JRE, there is no
// longer any need to call JNI_GetDefaultJavaVMInitArgs."
// 4) It seems that some platforms don't implement JNI_GetDefaultJavaVMInitArgs, or have
// marked it deprecated.
// Omitting the call to JNI_GetDefaultJavaVMInitArgs works fine on Mac and Linux with Java 7 and Java 8.
// The Oracle documentation is here:
// http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html
// http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html
// http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html
args.version = JNI_BEST_VERSION;
// JNI_GetDefaultJavaVMInitArgs(&args); // If this turns out to be necessary, it should be called here.
args.ignoreUnrecognized = false;
args.options = vmOptions;
args.nOptions = vmOptionsCount;
JavaVM* jvmTemp;
JNI_CreateJavaVM(&jvmTemp, (void **)env, &args);
*jvm = jvmTemp;
delete [] vmOptions;
m_classLoader = getSystemClassLoader(*env);
v8::Local onJvmCreated = this->handle()->Get(Nan::New("onJvmCreated").ToLocalChecked());
// TODO: this handles sets put doesn't prevent modifing the underlying data. So java.classpath.push will still work which is invalid.
Nan::SetAccessor(this->handle(), Nan::New("classpath").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
Nan::SetAccessor(this->handle(), Nan::New("options").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
Nan::SetAccessor(this->handle(), Nan::New("nativeBindingLocation").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
Nan::SetAccessor(this->handle(), Nan::New("asyncOptions").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
Nan::SetAccessor(this->handle(), Nan::New("onJvmCreated").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
if (onJvmCreated->IsFunction()) {
v8::Local onJvmCreatedFunc = onJvmCreated.As();
v8::Local context = Nan::New();
onJvmCreatedFunc->Call(context, 0, NULL);
}
return Nan::Null();
}
NAN_GETTER(Java::AccessorProhibitsOverwritingGetter) {
Java* self = Nan::ObjectWrap::Unwrap(info.This());
Nan::HandleScope scope;
v8::String::Utf8Value nameStr(property);
if(!strcmp("classpath", *nameStr)) {
info.GetReturnValue().Set(Nan::New(self->m_classPathArray));
return;
} else if(!strcmp("options", *nameStr)) {
info.GetReturnValue().Set(Nan::New(self->m_optionsArray));
return;
} else if(!strcmp("nativeBindingLocation", *nameStr)) {
info.GetReturnValue().Set(Nan::New(Java::s_nativeBindingLocation.c_str()).ToLocalChecked());
return;
} else if(!strcmp("asyncOptions", *nameStr)) {
info.GetReturnValue().Set(Nan::New(self->m_asyncOptions));
return;
} else if(!strcmp("onJvmCreated", *nameStr)) {
// There is no good reason to get onJvmCreated, so just fall through to error below.
}
std::ostringstream errStr;
errStr FindClass("java/lang/ClassLoader");
jmethodID class_getClassLoader = env->GetStaticMethodID(classClazz, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
jobject classLoader = env->CallStaticObjectMethod(classClazz, class_getClassLoader);
checkJavaException(env);
jobject result = env->NewGlobalRef(classLoader);
info.GetReturnValue().Set(javaToV8(self, env, result));
}
NAN_METHOD(Java::newInstance) {
Nan::HandleScope scope;
Java* self = Nan::ObjectWrap::Unwrap(info.This());
v8::Local ensureJvmResults = self->ensureJvm();
if(!ensureJvmResults->IsNull()) {
info.GetReturnValue().Set(ensureJvmResults);
return;
}
JNIEnv* env = self->getJavaEnv();
JavaScope javaScope(env);
int argsStart = 0;
int argsEnd = info.Length();
// arguments
ARGS_FRONT_CLASSNAME();
ARGS_BACK_CALLBACK();
// find class
jclass clazz = javaFindClass(env, className);
if(clazz == NULL) {
EXCEPTION_CALL_CALLBACK(self, "Could not find class " run();
END_CALLBACK_FUNCTION("\"Constructor for class '" GetEnv returned " + to_string(ret);
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
return;
}
Nan::HandleScope scope;
v8::Array* v8Args;
v8::Function* fn;
v8::Handle* argv;
int argc;
int i;
v8::Local v8Result;
jobject javaResult;
v8::Local dynamicProxyDataFunctions = Nan::New(dynamicProxyData->functions);
v8::Local fnObj = dynamicProxyDataFunctions->Get(Nan::New(dynamicProxyData->methodName.c_str()).ToLocalChecked());
if(fnObj->IsUndefined() || fnObj->IsNull()) {
dynamicProxyData->throwableClass = "java/lang/NoSuchMethodError";
dynamicProxyData->throwableMessage = "Could not find js function " + dynamicProxyData->methodName;
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
return;
}
if(!fnObj->IsFunction()) {
dynamicProxyData->throwableClass = "java/lang/IllegalStateException";
dynamicProxyData->throwableMessage = dynamicProxyData->methodName + " is not a function";
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
return;
}
fn = v8::Function::Cast(*fnObj);
if(dynamicProxyData->args) {
v8Args = v8::Array::Cast(*javaArrayToV8(dynamicProxyData->java, env, dynamicProxyData->args));
argc = v8Args->Length();
} else {
argc = 0;
}
argv = new v8::Handle[argc];
for(i=0; iGet(i);
}
v8::TryCatch tryCatch;
v8Result = fn->Call(dynamicProxyDataFunctions, argc, argv);
delete[] argv;
if (tryCatch.HasCaught()) {
dynamicProxyData->throwableClass = "node/NodeJsException";
v8::String::Utf8Value stackTrace(tryCatch.StackTrace());
if (stackTrace.length() > 0) {
dynamicProxyData->throwableMessage = std::string(*stackTrace);
} else {
v8::String::Utf8Value exception(tryCatch.Exception());
dynamicProxyData->throwableMessage = std::string(*exception);
}
tryCatch.Reset();
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
return;
}
if(!dynamicProxyDataVerify(dynamicProxyData)) {
return;
}
javaResult = v8ToJava(env, v8Result);
if(javaResult == NULL) {
dynamicProxyData->result = NULL;
} else {
dynamicProxyData->result = env->NewGlobalRef(javaResult);
}
dynamicProxyData->done = true;
}
void throwNewThrowable(JNIEnv* env, const char * excClassName, std::string msg) {
jclass newExcCls = env->FindClass(excClassName);
jthrowable throwable = env->ExceptionOccurred();
if (throwable != NULL) {
env->Throw(throwable); // this should only be Errors, according to the docs
}
env->ThrowNew(newExcCls, msg.c_str());
}
JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jobject src, jlong ptr, jobject method, jobjectArray args) {
long myThreadId = my_getThreadId();
bool hasArgsGlobalRef = false;
// args needs to be global, you can't send env across thread boundaries
DynamicProxyData* dynamicProxyData = (DynamicProxyData*)ptr;
dynamicProxyData->args = args;
dynamicProxyData->done = false;
dynamicProxyData->result = NULL;
dynamicProxyData->throwableClass = "";
dynamicProxyData->throwableMessage = "";
jclass methodClazz = env->FindClass("java/lang/reflect/Method");
jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;");
dynamicProxyData->methodName = javaObjectToString(env, env->CallObjectMethod(method, method_getName));
assertNoException(env);
uv_work_t* req = new uv_work_t();
req->data = dynamicProxyData;
if(myThreadId == v8ThreadId) {
#if NODE_MINOR_VERSION >= 10
EIO_AfterCallJs(req, 0);
#else
EIO_AfterCallJs(req);
#endif
} else {
if (args) {
// if args is not null and we have to kick this across the thread boundary, make it a global ref
dynamicProxyData->args = (jobjectArray) env->NewGlobalRef(args);
hasArgsGlobalRef = true;
}
uv_queue_work(uv_default_loop(), req, EIO_CallJs, (uv_after_work_cb)EIO_AfterCallJs);
while(!dynamicProxyData->done) {
my_sleep(100);
}
}
if(!dynamicProxyDataVerify(dynamicProxyData)) {
throwNewThrowable(env, "java/lang/IllegalStateException", "dynamicProxyData was corrupted");
}
if(hasArgsGlobalRef) {
env->DeleteGlobalRef(dynamicProxyData->args);
}
if (dynamicProxyData->done == DYNAMIC_PROXY_JS_ERROR) {
throwNewThrowable(env, dynamicProxyData->throwableClass.c_str(), dynamicProxyData->throwableMessage);
}
jobject result = NULL;
if(dynamicProxyData->result) {
// need to retain a local ref so that we can return it, otherwise the returned object gets corrupted
result = env->NewLocalRef(dynamicProxyData->result);
env->DeleteGlobalRef(dynamicProxyData->result);
}
return result;
}
JNIEXPORT void JNICALL Java_node_NodeDynamicProxyClass_unref(JNIEnv *env, jobject src, jlong ptr) {
DynamicProxyData* dynamicProxyData = (DynamicProxyData*)ptr;
unref(dynamicProxyData);
}