blob: 1243013fcba0216f90a4f41ee58c8d733953d8b5 [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.
#include "InteropUtil.h"
using namespace std;
// Function signatuire for creating java vm
typedef jint(JNICALL *JNI_CreateJavaVM_FN)(JavaVM **pvm, void **penv, void *args);
// The environment variable that points to java directory.
LPCTSTR JAVA_HOME = L"JAVA_HOME";
// Where should we try to load jvm dll from?
// Try server version first. Path relative to $(JAVA_HOME)
LPCTSTR JVM_DLL1 = L"\\jre\\bin\\server\\jvm.dll";
// If we could not find server jvm, Try client version.
// Path relative to $(JAVA_HOME)
LPCTSTR JVM_DLL2 = L"\\jre\\bin\\client\\jvm.dll";
// Name of the function that creates a java VM
const char* JNI_CreateJavaVM_Func_Name = "JNI_CreateJavaVM";
// Sometimes classpath is split into 2 arguments. Compensate for it.
const char* JavaOptionClassPath = "-classpath";
// JNI signature for Java String.
const char* JavaMainMethodSignature = "([Ljava/lang/String;)V";
// class name for NativeInterop
const char* NativeInteropClass = "org/apache/reef/javabridge/NativeInterop";
const int maxPathBufSize = 16 * 1024;
const char ClassPathSeparatorCharForWindows = ';';
// we look for this to delineate java vm arguments from app arguments.
// TODO: be smarter about this. Accomodate arbitrary apps that doesn't
// contain the term REEFLauncher.
const char* launcherClassSearchStr = "REEFLauncher";
// method to invoke
const char* JavaMainMethodName = "main";
//Prefix for classpath. This is how we tell JNI about the classpath.
const char* JavaClassPath = "-Djava.class.path=";
typedef enum {
ErrSuccess = 0,
ErrGetEnvironmentVariable = 1,
ErrLoadLibraryJVM = 2,
ErrGetProcAddress = 3,
ErrCreateJavaVM = 4,
ErrFindClassEntry = 5,
ErrGetStaticMethodID = 6,
ErrFindClassString = 7,
ErrNewObjectArray = 8,
} ErrorEnum;
//
// figure out where jvm options end, entry class method and its parameters begin
//
void GetCounts(
int cArgs,
char*argv[],
int& optionCount,
int& firstOptionOrdinal,
int& argCount,
int& firstArgOrdinal)
{
bool option = true;
optionCount = 0;
firstOptionOrdinal = 2;
argCount = 0;
firstArgOrdinal = -1;
for (int i = firstOptionOrdinal; i < cArgs; i++) {
if (option && NULL != strstr(argv[i], launcherClassSearchStr)) {
option = false;
firstArgOrdinal = i;
}
if (option) {
++optionCount;
}
else {
++argCount;
}
}
if (firstArgOrdinal < 0) {
throw gcnew ArgumentException("Unable to find a REEF Launcher");
}
}
//
// Set JVM option. JNI does not support unicode
//
void SetOption(JavaVMOption *option, char *src)
{
size_t len = strlen(src) + 1;
char* pszOption = new char[len];
strcpy_s(pszOption, len, src);
option->optionString = pszOption;
option->extraInfo = nullptr;
}
//
// Jni does not expand * and *.jar
// We need to do it ourselves.
//
char *ExpandJarPaths(char *jarPaths)
{
String^ classPathSeparatorStringForWindows = L";";
const char classPathSeparatorCharForrWindows = ';';
String^ jarExtension = L".jar";
String^ anyJarExtension = L"*.jar";
String^ asterisk = L"*";
const int StringBuilderInitalSize = 1024 * 16;
System::Text::StringBuilder^ sb = gcnew System::Text::StringBuilder(StringBuilderInitalSize);
sb->Append(gcnew String(JavaClassPath));
String^ pathString = gcnew String(jarPaths);
array<String^>^ rawPaths = pathString->Split(classPathSeparatorCharForrWindows);
for (int i = 0; i < rawPaths->Length; i++)
{
String^ oldPath = rawPaths[i];
int oldPathLength = oldPath->Length;
String^ path;
bool shouldExpand = false;
if (oldPath->EndsWith(asterisk))
{
path = oldPath + jarExtension;
shouldExpand = true;
}
else if (oldPath->EndsWith(anyJarExtension))
{
path = oldPath;
shouldExpand = true;
}
else
{
sb->Append(classPathSeparatorStringForWindows);
sb->Append(oldPath);
}
if (shouldExpand)
{
try
{
auto filesDir = System::IO::Path::GetDirectoryName(path);
auto fileName = System::IO::Path::GetFileName(path);
auto directoryInfo = gcnew System::IO::DirectoryInfo(filesDir);
auto files = directoryInfo->GetFiles(fileName);
for (int i = 0; i < files->Length; i++)
{
auto fullName = System::IO::Path::Combine(files[i]->Directory->ToString(), files[i]->ToString());
sb->Append(classPathSeparatorStringForWindows);
sb->Append(fullName);
}
}
catch (System::IO::DirectoryNotFoundException^)
{
//Ignore invalid paths.
}
}
}
auto newPaths = sb->ToString();
int len = newPaths->Length;
char* finalPath = new char[len + 1];
auto hglobal = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(newPaths);
memcpy(finalPath, (void*)hglobal, len + 1);
System::Runtime::InteropServices::Marshal::FreeHGlobal(hglobal);
return finalPath;
}
JavaVMOption* GetJavaOptions(char *argv[], int& optionCount, int firstOptionOrdinal)
{
JavaVMOption* options = new JavaVMOption[optionCount + 1];
char classPathBuf[maxPathBufSize];
int sourceOrdinal = firstOptionOrdinal;
for (int i = 0; i < optionCount; i++) {
SetOption(options + i, argv[sourceOrdinal]);
if (0 == strcmp(argv[i + firstOptionOrdinal], JavaOptionClassPath) && ((i + 1) < optionCount)) {
strcpy_s(classPathBuf, argv[++sourceOrdinal]);
for (char* ptr = classPathBuf; *ptr; ptr++) {
if (*ptr == '/') {
*ptr = '\\';
}
}
strcat_s(classPathBuf, ";local\\*;global\\*");
auto expandedPath = ExpandJarPaths(classPathBuf);
SetOption(options + i, expandedPath);
--optionCount;
}
++sourceOrdinal;
}
return options;
}
int Get_CreateJavaVM_Function(JNI_CreateJavaVM_FN& fn_JNI_CreateJavaVM)
{
wchar_t jvmDllPath1[maxPathBufSize];
wchar_t jvmDllPath2[maxPathBufSize];
DWORD rc = GetEnvironmentVariable(JAVA_HOME, jvmDllPath1, maxPathBufSize);
if (0 == rc) {
wprintf(L"Could not GetEnvironmentVariable %ls\n", JAVA_HOME);
return ErrGetEnvironmentVariable;
}
wcscat_s(jvmDllPath1, maxPathBufSize, JVM_DLL1);
HMODULE jvm_dll = LoadLibrary(jvmDllPath1);
if (jvm_dll == NULL) {
wprintf(L"Could not load dll %ls\n", jvmDllPath1);
GetEnvironmentVariable(JAVA_HOME, jvmDllPath2, maxPathBufSize);
wcscat_s(jvmDllPath2, maxPathBufSize, JVM_DLL2);
jvm_dll = LoadLibrary(jvmDllPath2);
if (jvm_dll == NULL) {
wprintf(L"Could not load dll %ls\n", jvmDllPath2);
return ErrLoadLibraryJVM;
}
}
fn_JNI_CreateJavaVM = (JNI_CreateJavaVM_FN)GetProcAddress(jvm_dll, JNI_CreateJavaVM_Func_Name);
if (fn_JNI_CreateJavaVM == NULL) {
printf("Could not GetProcAddress %s\n", JNI_CreateJavaVM_Func_Name);
return ErrGetProcAddress;
}
return ErrSuccess;
}
//
// Creates Java vm with the given options
//
int CreateJVM(JNIEnv*& env, JavaVM*& jvm, JavaVMOption* options, int optionCount) {
JNI_CreateJavaVM_FN fn_JNI_CreateJavaVM;
int failureCode = 0;
if ((failureCode = Get_CreateJavaVM_Function(fn_JNI_CreateJavaVM)) != ErrSuccess) {
return failureCode;
}
for (int i = 0; i < optionCount; i++) {
printf("Option %d [%s]\n", i, options[i].optionString);
}
fflush(stdout);
JavaVMInitArgs vm_args;
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = optionCount;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_FALSE;
long status = fn_JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (status) {
printf("Could not fn_JNI_CreateJavaVM\n");
return ErrCreateJavaVM;
}
return ErrSuccess;
}
//
// Invokes main method of entry class.
// I.E. org/apache/reef/runtime/common/REEFLauncher
//
int CallMainMethodOfEntryClass(
JNIEnv* env,
char* argv[],
int firstArgOrdinal,
int argCount)
{
// int the entry class name, Replace '.' with '/'
char classBuf[maxPathBufSize];
strcpy_s(classBuf, argv[firstArgOrdinal]);
for (char* ptr = classBuf; *ptr; ptr++) {
if (*ptr == '.') {
*ptr = '/';
}
}
// Find the entry class
jclass mainClass = env->FindClass(classBuf);
if (!mainClass) {
printf("Failed to find class '%s'", classBuf);
fflush(stdout);
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
fflush(stdout);
return ErrFindClassEntry;
}
// find method 'static void main (String[] args)'
jmethodID mainMethod = env->GetStaticMethodID(mainClass, JavaMainMethodName, JavaMainMethodSignature);
if (!mainMethod) {
printf("Failed to find jmethodID of 'main'");
return ErrGetStaticMethodID;
}
// Find string class
jclass stringClass = env->FindClass("java/lang/String");
if (!stringClass) {
printf("Failed to find java/lang/String");
return ErrFindClassString;
}
stringClass = reinterpret_cast<jclass>(env->NewGlobalRef(stringClass));
// Allocate string[] for main method parameter
jobjectArray args = env->NewObjectArray(argCount - 1, stringClass, 0);
if (!args) {
printf("Failed to create args array");
return ErrNewObjectArray;
}
args = reinterpret_cast<jobjectArray>(env->NewGlobalRef(args));
// Copy parameters for main method
for (int i = 0; i < argCount - 1; ++i) {
env->SetObjectArrayElement(args, i, env->NewStringUTF(argv[firstArgOrdinal + 1 + i]));
}
// Find the _NativeInterop class
jclass nativeInteropClass = env->FindClass(NativeInteropClass);
if (nativeInteropClass) {
nativeInteropClass = reinterpret_cast<jclass>(env->NewGlobalRef(nativeInteropClass));
printf("Found class '%s'", NativeInteropClass);
Java_org_apache_reef_javabridge_NativeInterop_registerNatives(env, nativeInteropClass);
}
else {
printf("Did not find class '%s'", NativeInteropClass);
}
fflush(stdout);
// call main method with parameters
env->CallStaticVoidMethod(mainClass, mainMethod, args);
return ErrSuccess;
}
int main(int cArgs, char *argv[])
{
int optionCount;
int firstOptionOrdinal;
int argCount;
int firstArgOrdinal;
GetCounts(cArgs, argv, optionCount, firstOptionOrdinal, argCount, firstArgOrdinal);
JavaVMOption* options = GetJavaOptions(argv, optionCount, firstOptionOrdinal);
JNIEnv *env;
JavaVM *jvm;
int failureCode = 0;
if ((failureCode = CreateJVM(env, jvm, options, optionCount)) != ErrSuccess) {
fflush(stdout);
return failureCode;
}
if ((failureCode = CallMainMethodOfEntryClass(env, argv, firstArgOrdinal, argCount)) != ErrSuccess) {
fflush(stdout);
return failureCode;
}
// Check for errors.
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
// Finally, destroy the JavaVM
jvm->DestroyJavaVM();
return 0;
}