[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/yslimmm/node-java/master/src/java.cpp [Back]  [Original]

#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

#ifdef WIN32
  typedef long threadId;
#else
  typedef pthread_t threadId;
#endif

threadId 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
}

threadId my_getThreadId() {
#ifdef WIN32
  return (long)GetCurrentThreadId();
#else
  return pthread_self();
#endif
}

bool v8ThreadIdEquals(threadId a, threadId b) {
#ifdef WIN32
  return a == b;
#else
  return pthread_equal(a, b);
#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);
  }

  Nan::TryCatch tryCatch;
  tryCatch.SetCaptureMessage(true);
  v8Result = fn->Call(dynamicProxyDataFunctions, argc, argv);
  delete[] argv;
  if (tryCatch.HasCaught()) {
    dynamicProxyData->throwableClass = "node/NodeJsException";
    v8::String::Utf8Value message(tryCatch.Message()->Get());
    dynamicProxyData->throwableMessage = std::string(*message);
    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) {
  threadId 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(v8ThreadIdEquals(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);
}

Web Proxy Viewer  |  New URL  |  Original Page