jni_helper.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. #include "config.h"
  19. #include "exception.h"
  20. #include "jni_helper.h"
  21. #include "platform.h"
  22. #include "common/htable.h"
  23. #include "os/mutexes.h"
  24. #include "os/thread_local_storage.h"
  25. #include <stdio.h>
  26. #include <string.h>
  27. static struct htable *gClassRefHTable = NULL;
  28. /** The Native return types that methods could return */
  29. #define JVOID 'V'
  30. #define JOBJECT 'L'
  31. #define JARRAYOBJECT '['
  32. #define JBOOLEAN 'Z'
  33. #define JBYTE 'B'
  34. #define JCHAR 'C'
  35. #define JSHORT 'S'
  36. #define JINT 'I'
  37. #define JLONG 'J'
  38. #define JFLOAT 'F'
  39. #define JDOUBLE 'D'
  40. /**
  41. * MAX_HASH_TABLE_ELEM: The maximum no. of entries in the hashtable.
  42. * It's set to 4096 to account for (classNames + No. of threads)
  43. */
  44. #define MAX_HASH_TABLE_ELEM 4096
  45. /**
  46. * Length of buffer for retrieving created JVMs. (We only ever create one.)
  47. */
  48. #define VM_BUF_LENGTH 1
  49. void destroyLocalReference(JNIEnv *env, jobject jObject)
  50. {
  51. if (jObject)
  52. (*env)->DeleteLocalRef(env, jObject);
  53. }
  54. static jthrowable validateMethodType(JNIEnv *env, MethType methType)
  55. {
  56. if (methType != STATIC && methType != INSTANCE) {
  57. return newRuntimeError(env, "validateMethodType(methType=%d): "
  58. "illegal method type.\n", methType);
  59. }
  60. return NULL;
  61. }
  62. jthrowable newJavaStr(JNIEnv *env, const char *str, jstring *out)
  63. {
  64. jstring jstr;
  65. if (!str) {
  66. /* Can't pass NULL to NewStringUTF: the result would be
  67. * implementation-defined. */
  68. *out = NULL;
  69. return NULL;
  70. }
  71. jstr = (*env)->NewStringUTF(env, str);
  72. if (!jstr) {
  73. /* If NewStringUTF returns NULL, an exception has been thrown,
  74. * which we need to handle. Probaly an OOM. */
  75. return getPendingExceptionAndClear(env);
  76. }
  77. *out = jstr;
  78. return NULL;
  79. }
  80. jthrowable newCStr(JNIEnv *env, jstring jstr, char **out)
  81. {
  82. const char *tmp;
  83. if (!jstr) {
  84. *out = NULL;
  85. return NULL;
  86. }
  87. tmp = (*env)->GetStringUTFChars(env, jstr, NULL);
  88. if (!tmp) {
  89. return getPendingExceptionAndClear(env);
  90. }
  91. *out = strdup(tmp);
  92. (*env)->ReleaseStringUTFChars(env, jstr, tmp);
  93. return NULL;
  94. }
  95. jthrowable invokeMethod(JNIEnv *env, jvalue *retval, MethType methType,
  96. jobject instObj, const char *className,
  97. const char *methName, const char *methSignature, ...)
  98. {
  99. va_list args;
  100. jclass cls;
  101. jmethodID mid;
  102. jthrowable jthr;
  103. const char *str;
  104. char returnType;
  105. jthr = validateMethodType(env, methType);
  106. if (jthr)
  107. return jthr;
  108. jthr = globalClassReference(className, env, &cls);
  109. if (jthr)
  110. return jthr;
  111. jthr = methodIdFromClass(className, methName, methSignature,
  112. methType, env, &mid);
  113. if (jthr)
  114. return jthr;
  115. str = methSignature;
  116. while (*str != ')') str++;
  117. str++;
  118. returnType = *str;
  119. va_start(args, methSignature);
  120. if (returnType == JOBJECT || returnType == JARRAYOBJECT) {
  121. jobject jobj = NULL;
  122. if (methType == STATIC) {
  123. jobj = (*env)->CallStaticObjectMethodV(env, cls, mid, args);
  124. }
  125. else if (methType == INSTANCE) {
  126. jobj = (*env)->CallObjectMethodV(env, instObj, mid, args);
  127. }
  128. retval->l = jobj;
  129. }
  130. else if (returnType == JVOID) {
  131. if (methType == STATIC) {
  132. (*env)->CallStaticVoidMethodV(env, cls, mid, args);
  133. }
  134. else if (methType == INSTANCE) {
  135. (*env)->CallVoidMethodV(env, instObj, mid, args);
  136. }
  137. }
  138. else if (returnType == JBOOLEAN) {
  139. jboolean jbool = 0;
  140. if (methType == STATIC) {
  141. jbool = (*env)->CallStaticBooleanMethodV(env, cls, mid, args);
  142. }
  143. else if (methType == INSTANCE) {
  144. jbool = (*env)->CallBooleanMethodV(env, instObj, mid, args);
  145. }
  146. retval->z = jbool;
  147. }
  148. else if (returnType == JSHORT) {
  149. jshort js = 0;
  150. if (methType == STATIC) {
  151. js = (*env)->CallStaticShortMethodV(env, cls, mid, args);
  152. }
  153. else if (methType == INSTANCE) {
  154. js = (*env)->CallShortMethodV(env, instObj, mid, args);
  155. }
  156. retval->s = js;
  157. }
  158. else if (returnType == JLONG) {
  159. jlong jl = -1;
  160. if (methType == STATIC) {
  161. jl = (*env)->CallStaticLongMethodV(env, cls, mid, args);
  162. }
  163. else if (methType == INSTANCE) {
  164. jl = (*env)->CallLongMethodV(env, instObj, mid, args);
  165. }
  166. retval->j = jl;
  167. }
  168. else if (returnType == JINT) {
  169. jint ji = -1;
  170. if (methType == STATIC) {
  171. ji = (*env)->CallStaticIntMethodV(env, cls, mid, args);
  172. }
  173. else if (methType == INSTANCE) {
  174. ji = (*env)->CallIntMethodV(env, instObj, mid, args);
  175. }
  176. retval->i = ji;
  177. }
  178. va_end(args);
  179. jthr = (*env)->ExceptionOccurred(env);
  180. if (jthr) {
  181. (*env)->ExceptionClear(env);
  182. return jthr;
  183. }
  184. return NULL;
  185. }
  186. jthrowable constructNewObjectOfClass(JNIEnv *env, jobject *out, const char *className,
  187. const char *ctorSignature, ...)
  188. {
  189. va_list args;
  190. jclass cls;
  191. jmethodID mid;
  192. jobject jobj;
  193. jthrowable jthr;
  194. jthr = globalClassReference(className, env, &cls);
  195. if (jthr)
  196. return jthr;
  197. jthr = methodIdFromClass(className, "<init>", ctorSignature,
  198. INSTANCE, env, &mid);
  199. if (jthr)
  200. return jthr;
  201. va_start(args, ctorSignature);
  202. jobj = (*env)->NewObjectV(env, cls, mid, args);
  203. va_end(args);
  204. if (!jobj)
  205. return getPendingExceptionAndClear(env);
  206. *out = jobj;
  207. return NULL;
  208. }
  209. jthrowable methodIdFromClass(const char *className, const char *methName,
  210. const char *methSignature, MethType methType,
  211. JNIEnv *env, jmethodID *out)
  212. {
  213. jclass cls;
  214. jthrowable jthr;
  215. jmethodID mid = 0;
  216. jthr = globalClassReference(className, env, &cls);
  217. if (jthr)
  218. return jthr;
  219. jthr = validateMethodType(env, methType);
  220. if (jthr)
  221. return jthr;
  222. if (methType == STATIC) {
  223. mid = (*env)->GetStaticMethodID(env, cls, methName, methSignature);
  224. }
  225. else if (methType == INSTANCE) {
  226. mid = (*env)->GetMethodID(env, cls, methName, methSignature);
  227. }
  228. if (mid == NULL) {
  229. fprintf(stderr, "could not find method %s from class %s with "
  230. "signature %s\n", methName, className, methSignature);
  231. return getPendingExceptionAndClear(env);
  232. }
  233. *out = mid;
  234. return NULL;
  235. }
  236. jthrowable globalClassReference(const char *className, JNIEnv *env, jclass *out)
  237. {
  238. jthrowable jthr = NULL;
  239. jclass local_clazz = NULL;
  240. jclass clazz = NULL;
  241. int ret;
  242. mutexLock(&hdfsHashMutex);
  243. if (!gClassRefHTable) {
  244. gClassRefHTable = htable_alloc(MAX_HASH_TABLE_ELEM, ht_hash_string,
  245. ht_compare_string);
  246. if (!gClassRefHTable) {
  247. jthr = newRuntimeError(env, "htable_alloc failed\n");
  248. goto done;
  249. }
  250. }
  251. clazz = htable_get(gClassRefHTable, className);
  252. if (clazz) {
  253. *out = clazz;
  254. goto done;
  255. }
  256. local_clazz = (*env)->FindClass(env,className);
  257. if (!local_clazz) {
  258. jthr = getPendingExceptionAndClear(env);
  259. goto done;
  260. }
  261. clazz = (*env)->NewGlobalRef(env, local_clazz);
  262. if (!clazz) {
  263. jthr = getPendingExceptionAndClear(env);
  264. goto done;
  265. }
  266. ret = htable_put(gClassRefHTable, (void*)className, clazz);
  267. if (ret) {
  268. jthr = newRuntimeError(env, "htable_put failed with error "
  269. "code %d\n", ret);
  270. goto done;
  271. }
  272. *out = clazz;
  273. jthr = NULL;
  274. done:
  275. mutexUnlock(&hdfsHashMutex);
  276. (*env)->DeleteLocalRef(env, local_clazz);
  277. if (jthr && clazz) {
  278. (*env)->DeleteGlobalRef(env, clazz);
  279. }
  280. return jthr;
  281. }
  282. jthrowable classNameOfObject(jobject jobj, JNIEnv *env, char **name)
  283. {
  284. jthrowable jthr;
  285. jclass cls, clsClass = NULL;
  286. jmethodID mid;
  287. jstring str = NULL;
  288. const char *cstr = NULL;
  289. char *newstr;
  290. cls = (*env)->GetObjectClass(env, jobj);
  291. if (cls == NULL) {
  292. jthr = getPendingExceptionAndClear(env);
  293. goto done;
  294. }
  295. clsClass = (*env)->FindClass(env, "java/lang/Class");
  296. if (clsClass == NULL) {
  297. jthr = getPendingExceptionAndClear(env);
  298. goto done;
  299. }
  300. mid = (*env)->GetMethodID(env, clsClass, "getName", "()Ljava/lang/String;");
  301. if (mid == NULL) {
  302. jthr = getPendingExceptionAndClear(env);
  303. goto done;
  304. }
  305. str = (*env)->CallObjectMethod(env, cls, mid);
  306. if (str == NULL) {
  307. jthr = getPendingExceptionAndClear(env);
  308. goto done;
  309. }
  310. cstr = (*env)->GetStringUTFChars(env, str, NULL);
  311. if (!cstr) {
  312. jthr = getPendingExceptionAndClear(env);
  313. goto done;
  314. }
  315. newstr = strdup(cstr);
  316. if (newstr == NULL) {
  317. jthr = newRuntimeError(env, "classNameOfObject: out of memory");
  318. goto done;
  319. }
  320. *name = newstr;
  321. jthr = NULL;
  322. done:
  323. destroyLocalReference(env, cls);
  324. destroyLocalReference(env, clsClass);
  325. if (str) {
  326. if (cstr)
  327. (*env)->ReleaseStringUTFChars(env, str, cstr);
  328. (*env)->DeleteLocalRef(env, str);
  329. }
  330. return jthr;
  331. }
  332. /**
  333. * Get the global JNI environemnt.
  334. *
  335. * We only have to create the JVM once. After that, we can use it in
  336. * every thread. You must be holding the jvmMutex when you call this
  337. * function.
  338. *
  339. * @return The JNIEnv on success; error code otherwise
  340. */
  341. static JNIEnv* getGlobalJNIEnv(void)
  342. {
  343. JavaVM* vmBuf[VM_BUF_LENGTH];
  344. JNIEnv *env;
  345. jint rv = 0;
  346. jint noVMs = 0;
  347. jthrowable jthr;
  348. char *hadoopClassPath;
  349. const char *hadoopClassPathVMArg = "-Djava.class.path=";
  350. size_t optHadoopClassPathLen;
  351. char *optHadoopClassPath;
  352. int noArgs = 1;
  353. char *hadoopJvmArgs;
  354. char jvmArgDelims[] = " ";
  355. char *str, *token, *savePtr;
  356. JavaVMInitArgs vm_args;
  357. JavaVM *vm;
  358. JavaVMOption *options;
  359. rv = JNI_GetCreatedJavaVMs(&(vmBuf[0]), VM_BUF_LENGTH, &noVMs);
  360. if (rv != 0) {
  361. fprintf(stderr, "JNI_GetCreatedJavaVMs failed with error: %d\n", rv);
  362. return NULL;
  363. }
  364. if (noVMs == 0) {
  365. //Get the environment variables for initializing the JVM
  366. hadoopClassPath = getenv("CLASSPATH");
  367. if (hadoopClassPath == NULL) {
  368. fprintf(stderr, "Environment variable CLASSPATH not set!\n");
  369. return NULL;
  370. }
  371. optHadoopClassPathLen = strlen(hadoopClassPath) +
  372. strlen(hadoopClassPathVMArg) + 1;
  373. optHadoopClassPath = malloc(sizeof(char)*optHadoopClassPathLen);
  374. snprintf(optHadoopClassPath, optHadoopClassPathLen,
  375. "%s%s", hadoopClassPathVMArg, hadoopClassPath);
  376. // Determine the # of LIBHDFS_OPTS args
  377. hadoopJvmArgs = getenv("LIBHDFS_OPTS");
  378. if (hadoopJvmArgs != NULL) {
  379. hadoopJvmArgs = strdup(hadoopJvmArgs);
  380. for (noArgs = 1, str = hadoopJvmArgs; ; noArgs++, str = NULL) {
  381. token = strtok_r(str, jvmArgDelims, &savePtr);
  382. if (NULL == token) {
  383. break;
  384. }
  385. }
  386. free(hadoopJvmArgs);
  387. }
  388. // Now that we know the # args, populate the options array
  389. options = calloc(noArgs, sizeof(JavaVMOption));
  390. if (!options) {
  391. fputs("Call to calloc failed\n", stderr);
  392. free(optHadoopClassPath);
  393. return NULL;
  394. }
  395. options[0].optionString = optHadoopClassPath;
  396. hadoopJvmArgs = getenv("LIBHDFS_OPTS");
  397. if (hadoopJvmArgs != NULL) {
  398. hadoopJvmArgs = strdup(hadoopJvmArgs);
  399. for (noArgs = 1, str = hadoopJvmArgs; ; noArgs++, str = NULL) {
  400. token = strtok_r(str, jvmArgDelims, &savePtr);
  401. if (NULL == token) {
  402. break;
  403. }
  404. options[noArgs].optionString = token;
  405. }
  406. }
  407. //Create the VM
  408. vm_args.version = JNI_VERSION_1_2;
  409. vm_args.options = options;
  410. vm_args.nOptions = noArgs;
  411. vm_args.ignoreUnrecognized = 1;
  412. rv = JNI_CreateJavaVM(&vm, (void*)&env, &vm_args);
  413. if (hadoopJvmArgs != NULL) {
  414. free(hadoopJvmArgs);
  415. }
  416. free(optHadoopClassPath);
  417. free(options);
  418. if (rv != 0) {
  419. fprintf(stderr, "Call to JNI_CreateJavaVM failed "
  420. "with error: %d\n", rv);
  421. return NULL;
  422. }
  423. jthr = invokeMethod(env, NULL, STATIC, NULL,
  424. "org/apache/hadoop/fs/FileSystem",
  425. "loadFileSystems", "()V");
  426. if (jthr) {
  427. printExceptionAndFree(env, jthr, PRINT_EXC_ALL, "loadFileSystems");
  428. }
  429. }
  430. else {
  431. //Attach this thread to the VM
  432. vm = vmBuf[0];
  433. rv = (*vm)->AttachCurrentThread(vm, (void*)&env, 0);
  434. if (rv != 0) {
  435. fprintf(stderr, "Call to AttachCurrentThread "
  436. "failed with error: %d\n", rv);
  437. return NULL;
  438. }
  439. }
  440. return env;
  441. }
  442. /**
  443. * getJNIEnv: A helper function to get the JNIEnv* for the given thread.
  444. * If no JVM exists, then one will be created. JVM command line arguments
  445. * are obtained from the LIBHDFS_OPTS environment variable.
  446. *
  447. * Implementation note: we rely on POSIX thread-local storage (tls).
  448. * This allows us to associate a destructor function with each thread, that
  449. * will detach the thread from the Java VM when the thread terminates. If we
  450. * failt to do this, it will cause a memory leak.
  451. *
  452. * However, POSIX TLS is not the most efficient way to do things. It requires a
  453. * key to be initialized before it can be used. Since we don't know if this key
  454. * is initialized at the start of this function, we have to lock a mutex first
  455. * and check. Luckily, most operating systems support the more efficient
  456. * __thread construct, which is initialized by the linker.
  457. *
  458. * @param: None.
  459. * @return The JNIEnv* corresponding to the thread.
  460. */
  461. JNIEnv* getJNIEnv(void)
  462. {
  463. JNIEnv *env;
  464. THREAD_LOCAL_STORAGE_GET_QUICK();
  465. mutexLock(&jvmMutex);
  466. if (threadLocalStorageGet(&env)) {
  467. mutexUnlock(&jvmMutex);
  468. return NULL;
  469. }
  470. if (env) {
  471. mutexUnlock(&jvmMutex);
  472. return env;
  473. }
  474. env = getGlobalJNIEnv();
  475. mutexUnlock(&jvmMutex);
  476. if (!env) {
  477. fprintf(stderr, "getJNIEnv: getGlobalJNIEnv failed\n");
  478. return NULL;
  479. }
  480. if (threadLocalStorageSet(env)) {
  481. return NULL;
  482. }
  483. THREAD_LOCAL_STORAGE_SET_QUICK(env);
  484. return env;
  485. }
  486. int javaObjectIsOfClass(JNIEnv *env, jobject obj, const char *name)
  487. {
  488. jclass clazz;
  489. int ret;
  490. clazz = (*env)->FindClass(env, name);
  491. if (!clazz) {
  492. printPendingExceptionAndFree(env, PRINT_EXC_ALL,
  493. "javaObjectIsOfClass(%s)", name);
  494. return -1;
  495. }
  496. ret = (*env)->IsInstanceOf(env, obj, clazz);
  497. (*env)->DeleteLocalRef(env, clazz);
  498. return ret == JNI_TRUE ? 1 : 0;
  499. }
  500. jthrowable hadoopConfSetStr(JNIEnv *env, jobject jConfiguration,
  501. const char *key, const char *value)
  502. {
  503. jthrowable jthr;
  504. jstring jkey = NULL, jvalue = NULL;
  505. jthr = newJavaStr(env, key, &jkey);
  506. if (jthr)
  507. goto done;
  508. jthr = newJavaStr(env, value, &jvalue);
  509. if (jthr)
  510. goto done;
  511. jthr = invokeMethod(env, NULL, INSTANCE, jConfiguration,
  512. "org/apache/hadoop/conf/Configuration", "set",
  513. "(Ljava/lang/String;Ljava/lang/String;)V",
  514. jkey, jvalue);
  515. if (jthr)
  516. goto done;
  517. done:
  518. (*env)->DeleteLocalRef(env, jkey);
  519. (*env)->DeleteLocalRef(env, jvalue);
  520. return jthr;
  521. }
  522. jthrowable fetchEnumInstance(JNIEnv *env, const char *className,
  523. const char *valueName, jobject *out)
  524. {
  525. jclass clazz;
  526. jfieldID fieldId;
  527. jobject jEnum;
  528. char prettyClass[256];
  529. clazz = (*env)->FindClass(env, className);
  530. if (!clazz) {
  531. return newRuntimeError(env, "fetchEnum(%s, %s): failed to find class.",
  532. className, valueName);
  533. }
  534. if (snprintf(prettyClass, sizeof(prettyClass), "L%s;", className)
  535. >= sizeof(prettyClass)) {
  536. return newRuntimeError(env, "fetchEnum(%s, %s): class name too long.",
  537. className, valueName);
  538. }
  539. fieldId = (*env)->GetStaticFieldID(env, clazz, valueName, prettyClass);
  540. if (!fieldId) {
  541. return getPendingExceptionAndClear(env);
  542. }
  543. jEnum = (*env)->GetStaticObjectField(env, clazz, fieldId);
  544. if (!jEnum) {
  545. return getPendingExceptionAndClear(env);
  546. }
  547. *out = jEnum;
  548. return NULL;
  549. }