3
/* Copyright 2009 10gen Inc.
5
* Licensed under the Apache License, Version 2.0 (the "License");
6
* you may not use this file except in compliance with the License.
7
* You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
20
#include "engine_java.h"
25
#include "../db/jsobj.h"
28
using namespace boost::filesystem;
36
#define JNI_DEBUG(x) cout << x << endl
46
#include "../util/message.h"
54
/* [dm] this being undefined without us adding it here means there is
55
no tss cleanup on windows for boost lib?
56
we don't care for now esp on windows only
58
the boost source says:
60
This function's sole purpose is to cause a link error in cases where
61
automatic tss cleanup is not implemented by Boost.Threads as a
62
reminder that user code is responsible for calling the necessary
63
functions at the appropriate times (and for implementing an a
64
tss_cleanup_implemented() function to eliminate the linker's
65
missing symbol error).
67
If Boost.Threads later implements automatic tss cleanup in cases
68
where it currently doesn't (which is the plan), the duplicate
69
symbol error will warn the user that their custom solution is no
70
longer needed and can be removed.
72
extern "C" void tss_cleanup_implemented(void) {
73
//out() << "tss_cleanup_implemented called" << endl;
77
JavaJSImpl * JavaJS = 0;
78
extern string dbExecCommand;
82
void myJNIClean( JNIEnv * env ) {
83
JavaJS->detach( env );
87
const char SYSTEM_COLON = ';';
89
const char SYSTEM_COLON = ':';
93
void _addClassPath( const char * ed , stringstream & ss , const char * subdir ) {
96
directory_iterator end;
98
directory_iterator i(includeDir);
101
ss << SYSTEM_COLON << p.string();
106
problem() << "exception looking for ed class path includeDir: " << includeDir.string() << endl;
113
JavaJSImpl::JavaJSImpl(const char *appserverPath) {
122
ss << "-Djava.class.path=.";
124
if ( appserverPath ) {
125
ed = findEd(appserverPath);
128
ss << SYSTEM_COLON << ed << "/build/";
130
_addClassPath( ed , ss , "include" );
131
_addClassPath( ed , ss , "include/jython/" );
132
_addClassPath( ed , ss , "include/jython/javalib" );
135
const string jars = findJars();
136
_addClassPath( jars.c_str() , ss , "jars" );
138
edTemp += (string)jars + "/jars/mongojs-js.jar";
145
ss << SYSTEM_COLON << "C:\\Program Files\\Java\\jdk\\lib\\tools.jar";
147
ss << SYSTEM_COLON << "/opt/java/lib/tools.jar";
150
if ( getenv( "CLASSPATH" ) )
151
ss << SYSTEM_COLON << getenv( "CLASSPATH" );
154
char * p = (char *)malloc( s.size() * 4 );
155
strcpy( p , s.c_str() );
159
if ( *p == '/' ) *p = '\\';
164
log(1) << "classpath: " << q << endl;
166
JavaVMOption * options = new JavaVMOption[4];
167
options[0].optionString = q;
168
options[1].optionString = (char*)"-Djava.awt.headless=true";
169
options[2].optionString = (char*)"-Xmx300m";
171
// Prevent JVM from using async signals internally, since on linux the pre-installed handlers for these
172
// signals don't seem to be respected by JNI.
173
options[3].optionString = (char*)"-Xrs";
176
_vmArgs = new JavaVMInitArgs();
177
_vmArgs->version = JNI_VERSION_1_4;
178
_vmArgs->options = options;
179
_vmArgs->nOptions = 4;
180
_vmArgs->ignoreUnrecognized = JNI_FALSE;
182
log(1) << "loading JVM" << endl;
183
jint res = JNI_CreateJavaVM( &_jvm, (void**)&_mainEnv, _vmArgs );
186
log() << "using classpath: " << q << endl;
188
<< " res : " << (unsigned) res << " "
189
<< "_jvm : " << _jvm << " "
190
<< "_env : " << _mainEnv << " "
192
problem() << "Couldn't create JVM res:" << (int) res << " terminating" << endl;
193
log() << "(try --nojni if you do not require that functionality)" << endl;
198
jassert( _mainEnv > 0 );
200
_envs = new boost::thread_specific_ptr<JNIEnv>( myJNIClean );
201
assert( ! _envs->get() );
202
_envs->reset( _mainEnv );
204
_dbhook = findClass( "ed/db/JSHook" );
205
if ( _dbhook == 0 ) {
206
log() << "using classpath: " << q << endl;
212
jmethodID init = _mainEnv->GetStaticMethodID( _dbhook , "init" , "(Ljava/lang/String;)V" );
214
_mainEnv->CallStaticVoidMethod( _dbhook , init , _getEnv()->NewStringUTF( ed ) );
217
_dbjni = findClass( "ed/db/DBJni" );
220
_scopeCreate = _mainEnv->GetStaticMethodID( _dbhook , "scopeCreate" , "()J" );
221
_scopeInit = _mainEnv->GetStaticMethodID( _dbhook , "scopeInit" , "(JLjava/nio/ByteBuffer;)Z" );
222
_scopeSetThis = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetThis" , "(JLjava/nio/ByteBuffer;)Z" );
223
_scopeReset = _mainEnv->GetStaticMethodID( _dbhook , "scopeReset" , "(J)Z" );
224
_scopeFree = _mainEnv->GetStaticMethodID( _dbhook , "scopeFree" , "(J)V" );
226
_scopeGetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetNumber" , "(JLjava/lang/String;)D" );
227
_scopeGetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetString" , "(JLjava/lang/String;)Ljava/lang/String;" );
228
_scopeGetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetBoolean" , "(JLjava/lang/String;)Z" );
229
_scopeGetType = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetType" , "(JLjava/lang/String;)B" );
230
_scopeGetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)I" );
231
_scopeGuessObjectSize = _mainEnv->GetStaticMethodID( _dbhook , "scopeGuessObjectSize" , "(JLjava/lang/String;)J" );
233
_scopeSetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetNumber" , "(JLjava/lang/String;D)Z" );
234
_scopeSetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetBoolean" , "(JLjava/lang/String;Z)Z" );
235
_scopeSetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetString" , "(JLjava/lang/String;Ljava/lang/String;)Z" );
236
_scopeSetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)Z" );
238
_functionCreate = _mainEnv->GetStaticMethodID( _dbhook , "functionCreate" , "(Ljava/lang/String;)J" );
239
_invoke = _mainEnv->GetStaticMethodID( _dbhook , "invoke" , "(JJ)I" );
241
jassert( _scopeCreate );
242
jassert( _scopeInit );
243
jassert( _scopeSetThis );
244
jassert( _scopeReset );
245
jassert( _scopeFree );
247
jassert( _scopeGetNumber );
248
jassert( _scopeGetString );
249
jassert( _scopeGetObject );
250
jassert( _scopeGetBoolean );
251
jassert( _scopeGetType );
252
jassert( _scopeGuessObjectSize );
254
jassert( _scopeSetNumber );
255
jassert( _scopeSetBoolean );
256
jassert( _scopeSetString );
257
jassert( _scopeSetObject );
259
jassert( _functionCreate );
262
JNINativeMethod * nativeSay = new JNINativeMethod();
263
nativeSay->name = (char*)"native_say";
264
nativeSay->signature = (char*)"(Ljava/nio/ByteBuffer;)V";
265
nativeSay->fnPtr = (void*)java_native_say;
266
_mainEnv->RegisterNatives( _dbjni , nativeSay , 1 );
269
JNINativeMethod * nativeCall = new JNINativeMethod();
270
nativeCall->name = (char*)"native_call";
271
nativeCall->signature = (char*)"(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I";
272
nativeCall->fnPtr = (void*)java_native_call;
273
_mainEnv->RegisterNatives( _dbjni , nativeCall , 1 );
277
JavaJSImpl::~JavaJSImpl() {
279
_jvm->DestroyJavaVM();
280
cout << "Destroying JVM" << endl;
286
jlong JavaJSImpl::scopeCreate() {
287
return _getEnv()->CallStaticLongMethod( _dbhook , _scopeCreate );
290
jboolean JavaJSImpl::scopeReset( jlong id ) {
291
return _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeReset );
294
void JavaJSImpl::scopeFree( jlong id ) {
295
_getEnv()->CallStaticVoidMethod( _dbhook , _scopeFree , id );
300
int JavaJSImpl::scopeSetBoolean( jlong id , const char * field , jboolean val ) {
301
jstring fieldString = _getEnv()->NewStringUTF( field );
302
int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val );
303
_getEnv()->DeleteLocalRef( fieldString );
307
int JavaJSImpl::scopeSetNumber( jlong id , const char * field , double val ) {
308
jstring fieldString = _getEnv()->NewStringUTF( field );
309
int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val );
310
_getEnv()->DeleteLocalRef( fieldString );
314
int JavaJSImpl::scopeSetString( jlong id , const char * field , const char * val ) {
315
jstring s1 = _getEnv()->NewStringUTF( field );
316
jstring s2 = _getEnv()->NewStringUTF( val );
317
int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetString , id , s1 , s2 );
318
_getEnv()->DeleteLocalRef( s1 );
319
_getEnv()->DeleteLocalRef( s2 );
323
int JavaJSImpl::scopeSetObject( jlong id , const char * field , const BSONObj * obj ) {
326
bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) );
330
jstring s1 = _getEnv()->NewStringUTF( field );
331
int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetObject , id , s1 , bb );
332
_getEnv()->DeleteLocalRef( s1 );
334
_getEnv()->DeleteLocalRef( bb );
339
int JavaJSImpl::scopeInit( jlong id , const BSONObj * obj ) {
343
jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) );
346
int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeInit , id , bb );
347
_getEnv()->DeleteLocalRef( bb );
351
int JavaJSImpl::scopeSetThis( jlong id , const BSONObj * obj ) {
355
jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) );
358
int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetThis , id , bb );
359
_getEnv()->DeleteLocalRef( bb );
365
char JavaJSImpl::scopeGetType( jlong id , const char * field ) {
366
jstring s1 = _getEnv()->NewStringUTF( field );
367
int res =_getEnv()->CallStaticByteMethod( _dbhook , _scopeGetType , id , s1 );
368
_getEnv()->DeleteLocalRef( s1 );
372
double JavaJSImpl::scopeGetNumber( jlong id , const char * field ) {
373
jstring s1 = _getEnv()->NewStringUTF( field );
374
double res = _getEnv()->CallStaticDoubleMethod( _dbhook , _scopeGetNumber , id , s1 );
375
_getEnv()->DeleteLocalRef( s1 );
379
jboolean JavaJSImpl::scopeGetBoolean( jlong id , const char * field ) {
380
jstring s1 = _getEnv()->NewStringUTF( field );
381
jboolean res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeGetBoolean , id , s1 );
382
_getEnv()->DeleteLocalRef( s1 );
386
string JavaJSImpl::scopeGetString( jlong id , const char * field ) {
387
jstring s1 = _getEnv()->NewStringUTF( field );
388
jstring s = (jstring)_getEnv()->CallStaticObjectMethod( _dbhook , _scopeGetString , id , s1 );
389
_getEnv()->DeleteLocalRef( s1 );
394
const char * c = _getEnv()->GetStringUTFChars( s , 0 );
396
_getEnv()->ReleaseStringUTFChars( s , c );
400
BSONObj JavaJSImpl::scopeGetObject( jlong id , const char * field )
402
jstring s1 = _getEnv()->NewStringUTF( field );
403
int guess = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGuessObjectSize , id , _getEnv()->NewStringUTF( field ) );
404
_getEnv()->DeleteLocalRef( s1 );
409
char * buf = (char *) malloc(guess);
410
jobject bb = _getEnv()->NewDirectByteBuffer( (void*)buf , guess );
413
int len = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGetObject , id , _getEnv()->NewStringUTF( field ) , bb );
414
_getEnv()->DeleteLocalRef( bb );
415
jassert( len > 0 && len < guess );
417
BSONObj obj(buf, true);
418
assert( obj.objsize() <= guess );
424
jlong JavaJSImpl::functionCreate( const char * code ) {
425
jstring s = _getEnv()->NewStringUTF( code );
427
jlong id = _getEnv()->CallStaticLongMethod( _dbhook , _functionCreate , s );
428
_getEnv()->DeleteLocalRef( s );
432
int JavaJSImpl::invoke( jlong scope , jlong function ) {
433
return _getEnv()->CallStaticIntMethod( _dbhook , _invoke , scope , function );
436
// --- fun run method
438
void JavaJSImpl::run( const char * js ) {
439
jclass c = findClass( "ed/js/JS" );
442
jmethodID m = _getEnv()->GetStaticMethodID( c , "eval" , "(Ljava/lang/String;)Ljava/lang/Object;" );
445
jstring s = _getEnv()->NewStringUTF( js );
446
log() << _getEnv()->CallStaticObjectMethod( c , m , s ) << endl;
447
_getEnv()->DeleteLocalRef( s );
450
void JavaJSImpl::printException() {
451
jthrowable exc = _getEnv()->ExceptionOccurred();
453
_getEnv()->ExceptionDescribe();
454
_getEnv()->ExceptionClear();
459
JNIEnv * JavaJSImpl::_getEnv() {
460
JNIEnv * env = _envs->get();
464
int res = _jvm->AttachCurrentThread( (void**)&env , (void*)&_vmArgs );
466
out() << "ERROR javajs attachcurrentthread fails res:" << res << '\n';
474
Scope * JavaJSImpl::createScope(){
475
return new JavaScope();
478
void ScriptEngine::setup(){
480
JavaJS = new JavaJSImpl();
481
globalScriptEngine = JavaJS;
485
void jasserted(const char *msg, const char *file, unsigned line) {
486
log() << "jassert failed " << msg << " " << file << " " << line << endl;
487
if ( JavaJS ) JavaJS->printException();
488
throw AssertionException();
492
const char* findEd(const char *path) {
500
// @TODO check validity
509
log() << "Appserver location specified : " << path << endl;
512
log() << " invalid appserver location : " << path << " : terminating - prepare for bus error" << endl;
516
DIR *testDir = opendir(path);
519
log(1) << " found directory for appserver : " << path << endl;
524
log() << " ERROR : not a directory for specified appserver location : " << path << " - prepare for bus error" << endl;
530
const char * findEd() {
533
log() << "Appserver location will be WIN32 default : c:/l/ed/" << endl;
537
static list<const char*> possibleEdDirs;
538
if ( ! possibleEdDirs.size() ) {
539
possibleEdDirs.push_back( "../../ed/ed/" ); // this one for dwight dev box
540
possibleEdDirs.push_back( "../ed/" );
541
possibleEdDirs.push_back( "../../ed/" );
542
possibleEdDirs.push_back( "../babble/" );
543
possibleEdDirs.push_back( "../../babble/" );
546
for ( list<const char*>::iterator i = possibleEdDirs.begin() ; i != possibleEdDirs.end(); i++ ) {
547
const char * temp = *i;
548
DIR * test = opendir( temp );
553
log(1) << "found directory for appserver : " << temp << endl;
561
const string findJars() {
563
static list<string> possible;
564
if ( ! possible.size() ) {
565
possible.push_back( "./" );
566
possible.push_back( "../" );
568
log(2) << "dbExecCommand: " << dbExecCommand << endl;
570
string dbDir = dbExecCommand;
572
if ( dbDir.find( "\\" ) != string::npos ){
573
dbDir = dbDir.substr( 0 , dbDir.find_last_of( "\\" ) );
579
if ( dbDir.find( "/" ) != string::npos ){
580
dbDir = dbDir.substr( 0 , dbDir.find_last_of( "/" ) );
585
if ( getenv( "PATH" ) ){
586
string s = getenv( "PATH" );
588
pcrecpp::StringPiece input( s );
590
pcrecpp::RE re("(.*?):");
591
while ( re.Consume( &input, &dir ) ){
592
string test = dir + "/" + dbExecCommand;
593
if ( boost::filesystem::exists( test ) ){
594
while ( boost::filesystem::symbolic_link_exists( test ) ){
596
int len = readlink( test.c_str() , tmp , 2048 );
598
log(5) << " symlink " << test << " -->> " << tmp << endl;
601
dir = test.substr( 0 , test.rfind( "/" ) );
615
log(2) << "dbDir [" << dbDir << "]" << endl;
616
possible.push_back( ( dbDir + "/../lib/mongo/" ));
617
possible.push_back( ( dbDir + "/../lib64/mongo/" ));
618
possible.push_back( ( dbDir + "/../lib32/mongo/" ));
619
possible.push_back( ( dbDir + "/" ));
620
possible.push_back( ( dbDir + "/lib64/mongo/" ));
621
possible.push_back( ( dbDir + "/lib32/mongo/" ));
624
for ( list<string>::iterator i = possible.begin() ; i != possible.end(); i++ ) {
625
const string temp = *i;
626
const string jarDir = ((string)temp) + "jars/";
628
log(5) << "possible jarDir [" << jarDir << "]" << endl;
631
if ( ! boost::filesystem::exists( p) )
634
log(1) << "found directory for jars : " << jarDir << endl;
638
problem() << "ERROR : can't find directory for jars - terminating" << endl;
647
JNIEXPORT void JNICALL java_native_say(JNIEnv * env , jclass, jobject outBuffer ) {
648
JNI_DEBUG( "native say called!" );
650
Message out( env->GetDirectBufferAddress( outBuffer ) , false );
653
jniCallback( out , in );
654
assert( ! out.doIFreeIt() );
658
JNIEXPORT jint JNICALL java_native_call(JNIEnv * env , jclass, jobject outBuffer , jobject inBuffer ) {
659
JNI_DEBUG( "native call called!" );
661
Message out( env->GetDirectBufferAddress( outBuffer ) , false );
664
jniCallback( out , in );
667
JNI_DEBUG( "in.data : " << in.data );
668
if ( in.data && in.data->len > 0 ) {
669
JNI_DEBUG( "copying data of len :" << in.data->len );
670
assert( env->GetDirectBufferCapacity( inBuffer ) >= in.data->len );
671
memcpy( env->GetDirectBufferAddress( inBuffer ) , in.data , in.data->len );
673
assert( ! out.doIFreeIt() );
674
assert( in.doIFreeIt() );
683
void JavaJSImpl::runTest() {
687
JavaJSImpl& JavaJS = *mongo::JavaJS;
689
jlong scope = JavaJS.scopeCreate();
691
if ( debug ) out() << "got scope" << endl;
694
jlong func1 = JavaJS.functionCreate( "foo = 5.6; bar = \"eliot\"; abc = { foo : 517 }; " );
695
jassert( ! JavaJS.invoke( scope , func1 ) );
698
if ( debug ) out() << "func3 start" << endl;
699
jlong func3 = JavaJS.functionCreate( "function(){ z = true; } " );
701
jassert( ! JavaJS.invoke( scope , func3 ) );
702
jassert( JavaJS.scopeGetBoolean( scope , "z" ) );
703
if ( debug ) out() << "func3 done" << endl;
705
if ( debug ) out() << "going to get object" << endl;
706
BSONObj obj = JavaJS.scopeGetObject( scope , "abc" );
707
if ( debug ) out() << "done getting object" << endl;
710
out() << "obj : " << obj.toString() << endl;
714
time_t start = time(0);
715
for ( int i=0; i<5000; i++ ) {
716
JavaJS.scopeSetObject( scope , "obj" , &obj );
718
time_t end = time(0);
721
out() << "time : " << (unsigned) ( end - start ) << endl;
724
if ( debug ) out() << "func4 start" << endl;
725
JavaJS.scopeSetObject( scope , "obj" , &obj );
726
if ( debug ) out() << "\t here 1" << endl;
727
jlong func4 = JavaJS.functionCreate( "tojson( obj );" );
728
if ( debug ) out() << "\t here 2" << endl;
729
jassert( ! JavaJS.invoke( scope , func4 ) );
730
if ( debug ) out() << "func4 end" << endl;
732
if ( debug ) out() << "func5 start" << endl;
733
jassert( JavaJS.scopeSetObject( scope , "c" , &obj ) );
734
jlong func5 = JavaJS.functionCreate( "assert.eq( 517 , c.foo );" );
736
jassert( ! JavaJS.invoke( scope , func5 ) );
737
if ( debug ) out() << "func5 done" << endl;
739
if ( debug ) out() << "func6 start" << endl;
740
for ( int i=0; i<100; i++ ) {
742
JavaJS.scopeSetNumber( scope , "zzz" , val );
743
jlong func6 = JavaJS.functionCreate( " xxx = zzz; " );
744
jassert( ! JavaJS.invoke( scope , func6 ) );
745
double n = JavaJS.scopeGetNumber( scope , "xxx" );
748
if ( debug ) out() << "func6 done" << endl;
750
jlong func7 = JavaJS.functionCreate( "return 11;" );
751
jassert( ! JavaJS.invoke( scope , func7 ) );
752
assert( 11 == JavaJS.scopeGetNumber( scope , "return" ) );
754
scope = JavaJS.scopeCreate();
755
jlong func8 = JavaJS.functionCreate( "function(){ return 12; }" );
756
jassert( ! JavaJS.invoke( scope , func8 ) );
757
assert( 12 == JavaJS.scopeGetNumber( scope , "return" ) );