javaembed.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "platform.h"
  14. #include <jni.h>
  15. #include "eclrtl.hpp"
  16. #include "jexcept.hpp"
  17. #include "jthread.hpp"
  18. #include "hqlplugins.hpp"
  19. #ifdef _WIN32
  20. #define EXPORT __declspec(dllexport)
  21. #else
  22. #define EXPORT
  23. #endif
  24. static const char * compatibleVersions[] = {
  25. "Java Embed Helper 1.0.0",
  26. NULL };
  27. static const char *version = "Java Embed Helper 1.0.0";
  28. static const char * EclDefinition =
  29. "EXPORT Language := SERVICE\n"
  30. " boolean getEmbedContext():cpp,pure,namespace='javaembed',entrypoint='getEmbedContext',prototype='IEmbedContext* getEmbedContext()';\n"
  31. "END;"
  32. "EXPORT getEmbedContext := Language.getEmbedContext;"
  33. "EXPORT boolean supportsImport := true;"
  34. "EXPORT boolean supportsScript := false;";
  35. extern "C" EXPORT bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
  36. {
  37. if (pb->size == sizeof(ECLPluginDefinitionBlockEx))
  38. {
  39. ECLPluginDefinitionBlockEx * pbx = (ECLPluginDefinitionBlockEx *) pb;
  40. pbx->compatibleVersions = compatibleVersions;
  41. }
  42. else if (pb->size != sizeof(ECLPluginDefinitionBlock))
  43. return false;
  44. pb->magicVersion = PLUGIN_VERSION;
  45. pb->version = version;
  46. pb->moduleName = "java";
  47. pb->ECL = EclDefinition;
  48. pb->flags = PLUGIN_DLL_MODULE | PLUGIN_MULTIPLE_VERSIONS;
  49. pb->description = "Java Embed Helper";
  50. return true;
  51. }
  52. namespace javaembed {
  53. // Use a global object to ensure that the Java VM is initialized once only.
  54. // We would like to create it lazily for two reasons:
  55. // 1. So that we only get a JVM if we need one (even if we have loaded the plugin)
  56. // 2. It's important for the JVM to be initialized AFTER we have set up signal handlers, as it
  57. // likes to set its own (in particular, it seems to intercept and ignore some SIGSEGV during the
  58. // garbage collection).
  59. // Unfortunately, it seems that the design of the JNI interface is such that JNI_CreateJavaVM has to be called on the 'main thread'.
  60. // So we can't achieve 1, and 2 requires that we create via the INIT_MODLE mechanism (rather than just a static object), and that
  61. // any engines that call InitModuleObjects() or load plugins dynamically do so AFTER setting any signal handlers or calling
  62. // EnableSEHtoExceptionMapping
  63. //
  64. static class JavaGlobalState
  65. {
  66. public:
  67. JavaGlobalState()
  68. {
  69. JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
  70. JavaVMOption* options = new JavaVMOption[3];
  71. options[0].optionString = (char *) "-Djava.class.path=.";
  72. options[1].optionString = (char *) "-Xcheck:jni";
  73. options[2].optionString = (char *) "-verbose:jni";
  74. vm_args.version = JNI_VERSION_1_6;
  75. #ifdef _DEBUG
  76. vm_args.nOptions = 1; // set to 3 if you want the verbose...
  77. #else
  78. vm_args.nOptions = 1;
  79. #endif
  80. vm_args.options = options;
  81. vm_args.ignoreUnrecognized = false;
  82. /* load and initialize a Java VM, return a JNI interface pointer in env */
  83. JNIEnv *env; /* receives pointer to native method interface */
  84. JNI_CreateJavaVM(&javaVM, (void**)&env, &vm_args);
  85. delete [] options;
  86. }
  87. ~JavaGlobalState()
  88. {
  89. // We don't attempt to destroy the Java VM, as it's buggy...
  90. }
  91. JavaVM *javaVM; /* denotes a Java VM */
  92. } *globalState;
  93. MODULE_INIT(INIT_PRIORITY_STANDARD)
  94. {
  95. globalState = new JavaGlobalState;
  96. return true;
  97. }
  98. MODULE_EXIT()
  99. {
  100. delete globalState;
  101. globalState = NULL;
  102. }
  103. // There is a singleton JavaThreadContext per thread. This allows us to
  104. // ensure that we can make repeated calls to a Java function efficiently.
  105. class JavaThreadContext
  106. {
  107. public:
  108. JNIEnv *JNIenv; /* receives pointer to native method interface */
  109. public:
  110. JavaThreadContext()
  111. {
  112. jint res = globalState->javaVM->AttachCurrentThread((void **) &JNIenv, NULL);
  113. assertex(res >= 0);
  114. javaClass = NULL;
  115. javaMethodID = NULL;
  116. }
  117. ~JavaThreadContext()
  118. {
  119. if (javaClass)
  120. JNIenv->DeleteGlobalRef(javaClass);
  121. }
  122. inline void importFunction(const char *text)
  123. {
  124. if (!prevtext || strcmp(text, prevtext) != 0)
  125. {
  126. prevtext.clear();
  127. // Name should be in the form class.method:signature
  128. const char *funcname = strchr(text, '.');
  129. if (!funcname)
  130. throw MakeStringException(MSGAUD_user, 0, "javaembed: Invalid import name %s - Expected classname.methodname:signature", text);
  131. const char *signature = strchr(funcname, ':');
  132. if (!signature)
  133. throw MakeStringException(MSGAUD_user, 0, "javaembed: Invalid import name %s - Expected classname.methodname:signature", text);
  134. StringBuffer classname(funcname-text, text);
  135. funcname++; // skip the '.'
  136. StringBuffer methodname(signature-funcname, funcname);
  137. signature++; // skip the ':'
  138. if (javaClass)
  139. JNIenv->DeleteGlobalRef(javaClass);
  140. javaClass = (jclass) JNIenv->NewGlobalRef(JNIenv->FindClass(classname));
  141. if (!javaClass)
  142. throw MakeStringException(MSGAUD_user, 0, "javaembed: Failed to resolve class name %s", classname.str());
  143. javaMethodID = JNIenv->GetStaticMethodID(javaClass, methodname, signature);
  144. if (!javaMethodID)
  145. throw MakeStringException(MSGAUD_user, 0, "javaembed: Failed to resolve method name %s with signature %s", methodname.str(), signature);
  146. const char *returnSig = strrchr(signature, ')');
  147. assertex(returnSig);
  148. returnSig++;
  149. returnType.set(returnSig);
  150. prevtext.set(text);
  151. }
  152. }
  153. inline void callFunction(jvalue &result, const jvalue * args)
  154. {
  155. switch (returnType.get()[0])
  156. {
  157. case 'C': UNIMPLEMENTED; // jchar has no real ecl equivalent
  158. case 'B': result.z = JNIenv->CallStaticBooleanMethodA(javaClass, javaMethodID, args); break;
  159. case 'J': result.j = JNIenv->CallStaticLongMethodA(javaClass, javaMethodID, args); break;
  160. case 'F': result.f = JNIenv->CallStaticFloatMethodA(javaClass, javaMethodID, args); break;
  161. case 'D': result.d = JNIenv->CallStaticDoubleMethodA(javaClass, javaMethodID, args); break;
  162. case 'L': result.l = JNIenv->CallStaticObjectMethodA(javaClass, javaMethodID, args); break;
  163. case 'I': // Others are all smaller ints, so we can use this for all
  164. default: result.i = JNIenv->CallStaticIntMethodA(javaClass, javaMethodID, args); break;
  165. }
  166. }
  167. inline __int64 getSignedResult(jvalue & result)
  168. {
  169. switch (returnType.get()[0])
  170. {
  171. case 'B': return result.b;
  172. case 'S': return result.s;
  173. case 'I': return result.i;
  174. case 'J': return result.j;
  175. case 'L':
  176. {
  177. // Result should be of class 'Number'
  178. jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "longValue", "()J");
  179. if (!getVal)
  180. throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
  181. return JNIenv->CallLongMethod(result.l, getVal);
  182. }
  183. default:
  184. throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
  185. }
  186. }
  187. inline double getDoubleResult(jvalue &result)
  188. {
  189. switch (returnType.get()[0])
  190. {
  191. case 'D': return result.d;
  192. case 'F': return result.f;
  193. case 'L':
  194. {
  195. // Result should be of class 'Number'
  196. jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "doubleValue", "()D");
  197. if (!getVal)
  198. throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
  199. return JNIenv->CallDoubleMethod(result.l, getVal);
  200. }
  201. default:
  202. throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
  203. }
  204. }
  205. inline bool getBooleanResult(jvalue &result)
  206. {
  207. switch (returnType.get()[0])
  208. {
  209. case 'Z': return result.z;
  210. case 'L':
  211. {
  212. // Result should be of class 'Boolean'
  213. jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "booleanValue", "()Z");
  214. if (!getVal)
  215. throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
  216. return JNIenv->CallBooleanMethod(result.l, getVal);
  217. }
  218. default:
  219. throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
  220. }
  221. }
  222. inline void getStringResult(jvalue &result, size32_t &__len, char * &__result)
  223. {
  224. jstring sresult = (jstring) result.l;
  225. __len = JNIenv->GetStringUTFLength(sresult);
  226. const char * chars = JNIenv->GetStringUTFChars(sresult, NULL);
  227. __result = (char *)rtlMalloc(__len);
  228. memcpy(__result, chars, __len);
  229. JNIenv->ReleaseStringUTFChars(sresult, chars);
  230. }
  231. private:
  232. StringAttr returnType;
  233. StringAttr prevtext;
  234. jclass javaClass;
  235. jmethodID javaMethodID;
  236. };
  237. // Each call to a Java function will use a new JavaEmbedScriptContext object
  238. #define MAX_JNI_ARGS 10
  239. class JavaEmbedImportContext : public CInterfaceOf<IEmbedFunctionContext>
  240. {
  241. public:
  242. JavaEmbedImportContext(JavaThreadContext *_sharedCtx, const char *options)
  243. : sharedCtx(_sharedCtx)
  244. {
  245. argcount = 0;
  246. }
  247. ~JavaEmbedImportContext()
  248. {
  249. }
  250. virtual bool getBooleanResult()
  251. {
  252. return sharedCtx->getBooleanResult(result);
  253. }
  254. virtual double getRealResult()
  255. {
  256. return sharedCtx->getDoubleResult(result);
  257. }
  258. virtual __int64 getSignedResult()
  259. {
  260. return sharedCtx->getSignedResult(result);
  261. }
  262. virtual unsigned __int64 getUnsignedResult()
  263. {
  264. throw MakeStringException(MSGAUD_user, 0, "javaembed: Unsigned results not supported"); // Java doesn't support unsigned
  265. }
  266. virtual void getStringResult(size32_t &__len, char * &__result)
  267. {
  268. sharedCtx->getStringResult(result, __len, __result);
  269. }
  270. virtual void bindBooleanParam(const char *name, bool val)
  271. {
  272. jvalue v;
  273. v.z = val;
  274. addArg(v);
  275. }
  276. virtual void bindRealParam(const char *name, double val)
  277. {
  278. jvalue v;
  279. v.d = val;
  280. addArg(v);
  281. }
  282. virtual void bindSignedParam(const char *name, __int64 val)
  283. {
  284. jvalue v;
  285. v.j = val;
  286. addArg(v);
  287. }
  288. virtual void bindUnsignedParam(const char *name, unsigned __int64 val)
  289. {
  290. throw MakeStringException(MSGAUD_user, 0, "javaembed: Unsigned parameters not supported"); // Java doesn't support unsigned
  291. }
  292. virtual void bindStringParam(const char *name, size32_t len, const char *val)
  293. {
  294. StringBuffer s(len, val);
  295. jvalue v;
  296. v.l = sharedCtx->JNIenv->NewStringUTF(s.str());
  297. addArg(v);
  298. }
  299. virtual void bindVStringParam(const char *name, const char *val)
  300. {
  301. jvalue v;
  302. v.l = sharedCtx->JNIenv->NewStringUTF(val);
  303. addArg(v);
  304. }
  305. virtual void importFunction(const char *text)
  306. {
  307. sharedCtx->importFunction(text);
  308. }
  309. virtual void callFunction()
  310. {
  311. sharedCtx->callFunction(result, args);
  312. }
  313. virtual void compileEmbeddedScript(const char *script)
  314. {
  315. throwUnexpected(); // The java language helper supports only imported functions, not embedding java code in ECL.
  316. }
  317. protected:
  318. JavaThreadContext *sharedCtx;
  319. jvalue result;
  320. private:
  321. void addArg(jvalue &arg)
  322. {
  323. assertex(argcount < MAX_JNI_ARGS);
  324. args[argcount] = arg;
  325. argcount++;
  326. }
  327. jvalue args[MAX_JNI_ARGS];
  328. int argcount;
  329. };
  330. static __thread JavaThreadContext* threadContext; // We reuse per thread, for speed
  331. static __thread ThreadTermFunc threadHookChain;
  332. static void releaseContext()
  333. {
  334. delete threadContext;
  335. threadContext = NULL;
  336. if (threadHookChain)
  337. (*threadHookChain)();
  338. }
  339. class JavaEmbedContext : public CInterfaceOf<IEmbedContext>
  340. {
  341. public:
  342. virtual IEmbedFunctionContext *createFunctionContext(bool isImport, const char *options)
  343. {
  344. if (!threadContext)
  345. {
  346. threadContext = new JavaThreadContext;
  347. threadHookChain = addThreadTermFunc(releaseContext);
  348. }
  349. assertex(isImport);
  350. return new JavaEmbedImportContext(threadContext, options);
  351. }
  352. };
  353. extern IEmbedContext* getEmbedContext()
  354. {
  355. return new JavaEmbedContext;
  356. }
  357. } // namespace