blob: 0aa0afd624b851ef8d2a3233bac9b739a8cea45e [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed 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.
*/
/**
* In process JNI worker. It'll call an arbitrary java class's main method
* with given parameters and set a pointer to the java vm in the workerEnv.
* ( XXX and an optional set method to pass env, workerEnv pointers )
*
* @author: Gal Shachor <shachor@il.ibm.com>
* @author: Costin Manolache
*/
#include "jk_workerEnv.h"
#include "jk_env.h"
#include "jk_bean.h"
#ifdef HAVE_JNI
#include "apr_thread_proc.h"
#include "jk_vm.h"
#include "jk_registry.h"
#include <jni.h>
extern jint jk_jni_aprImpl_registerNatives(JNIEnv *, jclass);
extern int jk_jni_status_code;
/* default only, will be configurable */
#define JAVA_BRIDGE_CLASS_NAME ("org/apache/jk/apr/TomcatStarter")
#define JAVA_BRIDGE_CLASS_APRI ("org/apache/jk/apr/AprImpl")
/* The class will be executed when the connector is started */
#define JK2_WORKER_HOOK_STARTUP 0
#define JK2_WORKER_HOOK_INIT 1
#define JK2_WORKER_HOOK_CLOSE 2
/* The class will be executed when the connector is about to be destroyed */
#define JK2_WORKER_HOOK_SHUTDOWN 3
struct jni_worker_data
{
jclass jk_java_bridge_class;
jclass jk_java_bridge_apri_class;
jmethodID jk_main_method;
jmethodID jk_setout_method;
jmethodID jk_seterr_method;
char *className;
char *stdout_name;
char *stderr_name;
/* Hack to allow multiple 'options' for the class name */
char **classNameOptions;
char **args;
int nArgs;
int hook;
};
typedef struct jni_worker_data jni_worker_data_t;
/** -------------------- Startup -------------------- */
/** Static methods - much easier...
*/
static int jk2_get_method_ids(jk_env_t *env, jni_worker_data_t * p,
JNIEnv * jniEnv)
{
p->jk_main_method =
(*jniEnv)->GetStaticMethodID(jniEnv, p->jk_java_bridge_class,
"main", "([Ljava/lang/String;)V");
if (!p->jk_main_method) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"Can't find main(String [])\n");
return JK_ERR;
}
/* Only the startup hook can redirect the stdout/stderr */
if (p->hook == JK2_WORKER_HOOK_STARTUP) {
p->jk_setout_method =
(*jniEnv)->GetStaticMethodID(jniEnv, p->jk_java_bridge_apri_class,
"setOut", "(Ljava/lang/String;)V");
if (!p->jk_setout_method) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"Can't find AprImpl.setOut(String)");
return JK_ERR;
}
p->jk_seterr_method =
(*jniEnv)->GetStaticMethodID(jniEnv, p->jk_java_bridge_apri_class,
"setErr", "(Ljava/lang/String;)V");
if (!p->jk_seterr_method) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"Can't find AprImpl.setErr(String)\n");
return JK_ERR;
}
}
return JK_OK;
}
static int JK_METHOD jk2_jni_worker_service(jk_env_t *env,
jk_worker_t *w,
jk_ws_service_t *s)
{
return JK_ERR;
}
static int JK_METHOD jk2_jni_worker_setProperty(jk_env_t *env,
jk_bean_t *mbean, char *name,
void *valueP)
{
jk_worker_t *_this = mbean->object;
char *value = valueP;
jni_worker_data_t *jniWorker;
int mem_config = 0;
if (!_this || !_this->worker_private) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"In validate, assert failed - invalid parameters\n");
return JK_ERR;
}
jniWorker = _this->worker_private;
if (strcmp(mbean->name, "worker.jni:onStartup") == 0)
jniWorker->hook = JK2_WORKER_HOOK_STARTUP;
else if (strncmp(mbean->name, "worker.jni:onInit",
sizeof("worker.jni:onInit") - 1) == 0)
jniWorker->hook = JK2_WORKER_HOOK_INIT;
else if (strncmp(mbean->name, "worker.jni:onClose",
sizeof("worker.jni:onClose") - 1) == 0)
jniWorker->hook = JK2_WORKER_HOOK_CLOSE;
else if (strcmp(mbean->name, "worker.jni:onShutdown") == 0)
jniWorker->hook = JK2_WORKER_HOOK_SHUTDOWN;
if (strcmp(name, "stdout") == 0) {
jniWorker->stdout_name = value;
}
else if (strcmp(name, "stderr") == 0) {
jniWorker->stderr_name = value;
}
else if (strcmp(name, "class") == 0) {
if (jniWorker->className != NULL) {
int i;
for (i = 0; i < 4; i++) {
if (jniWorker->classNameOptions[i] == NULL) {
jniWorker->classNameOptions[i] = value;
}
}
}
else {
jniWorker->className = value;
}
}
else if (strcmp(name, "ARG") == 0) {
jniWorker->args[jniWorker->nArgs] = value;
jniWorker->nArgs++;
}
else {
return JK_ERR;
}
return JK_OK;
}
static int JK_METHOD jk2_jni_worker_init(jk_env_t *env, jk_bean_t *bean)
{
jk_worker_t *_this = bean->object;
jni_worker_data_t *jniWorker;
JNIEnv *jniEnv;
jstring cmd_line = NULL;
jstring stdout_name = NULL;
jstring stderr_name = NULL;
jint rc = 0;
char *str_config = NULL;
jk_map_t *props;
jk_vm_t *vm;
jclass jstringClass;
jarray jargs;
int i = 0;
if (!_this || !_this->worker_private) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"In init, assert failed - invalid parameters\n");
return JK_ERR;
}
vm = _this->workerEnv->vm;
if (vm == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"workerJni.init() No VM found\n");
return JK_ERR;
}
/* Allow only the first child to execute the worker */
if (_this->workerEnv->childId != 0) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"workerJni.Init() Skipping initialization for the %d %d\n",
_this->workerEnv->childId,
_this->workerEnv->childProcessId);
return JK_ERR;
}
props = _this->workerEnv->initData;
jniWorker = _this->worker_private;
if (jniWorker->className == NULL)
jniWorker->className = JAVA_BRIDGE_CLASS_NAME;
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.validate() class= %s \n", jniWorker->className);
jniEnv = (JNIEnv *) vm->attach(env, vm);
if (jniEnv == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"workerJni.init() Can't attach to VM\n");
return JK_ERR;
}
if (jniWorker->stdout_name) {
stdout_name = (*jniEnv)->NewStringUTF(jniEnv, jniWorker->stdout_name);
}
if (jniWorker->stderr_name) {
stderr_name = (*jniEnv)->NewStringUTF(jniEnv, jniWorker->stderr_name);
}
jniWorker->jk_java_bridge_class =
(*jniEnv)->FindClass(jniEnv, jniWorker->className);
if (jniWorker->jk_java_bridge_class == NULL) {
int i;
for (i = 0; i < 4; i++) {
if (jniWorker->classNameOptions[i] != NULL) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.validate() try %s \n",
jniWorker->classNameOptions[i]);
jniWorker->jk_java_bridge_class =
(*jniEnv)->FindClass(jniEnv,
jniWorker->classNameOptions[i]);
if (jniWorker->jk_java_bridge_class != NULL)
break;
}
}
}
if (jniWorker->jk_java_bridge_class == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"Can't find class %s\n", jniWorker->className);
/* [V] the detach here may segfault on 1.1 JVM... */
vm->detach(env, vm);
return JK_ERR;
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"Loaded %s\n", jniWorker->className);
/* Instead of loading mod_jk2.so from java, use the JNI RegisterGlobals.
* XXX Need the way to customize JAVA_BRIDGE_CLASS_APRI, but since
* it's hardcoded in JniHandler.java doesn't matter for now.
*/
jniWorker->jk_java_bridge_apri_class =
(*jniEnv)->FindClass(jniEnv, JAVA_BRIDGE_CLASS_APRI);
if (jniWorker->jk_java_bridge_apri_class == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"Can't find class %s\n", JAVA_BRIDGE_CLASS_APRI);
/* [V] the detach here may segfault on 1.1 JVM... */
vm->detach(env, vm);
return JK_ERR;
}
if (jniWorker->hook == JK2_WORKER_HOOK_STARTUP) {
rc = jk_jni_aprImpl_registerNatives(jniEnv,
jniWorker->
jk_java_bridge_apri_class);
if (rc != 0) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"Can't register native functions for %s \n",
JAVA_BRIDGE_CLASS_APRI);
vm->detach(env, vm);
return JK_ERR;
}
}
rc = jk2_get_method_ids(env, jniWorker, jniEnv);
if (rc != JK_OK) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"jniWorker: Fail-> can't get method ids\n");
/* [V] the detach here may segfault on 1.1 JVM... */
vm->detach(env, vm);
return rc;
}
/* Set out and err stadard files */
if (jniWorker->stdout_name && jniWorker->jk_setout_method) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.init() setting stdout=%s...\n",
jniWorker->stdout_name);
(*jniEnv)->CallStaticVoidMethod(jniEnv,
jniWorker->jk_java_bridge_apri_class,
jniWorker->jk_setout_method,
stdout_name);
}
if (jniWorker->stderr_name && jniWorker->jk_seterr_method) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.init() setting stderr=%s...\n",
jniWorker->stderr_name);
(*jniEnv)->CallStaticVoidMethod(jniEnv,
jniWorker->jk_java_bridge_apri_class,
jniWorker->jk_seterr_method,
stderr_name);
}
if (jniWorker->hook < JK2_WORKER_HOOK_CLOSE) {
jstringClass = (*jniEnv)->FindClass(jniEnv, "java/lang/String");
jargs =
(*jniEnv)->NewObjectArray(jniEnv, jniWorker->nArgs, jstringClass,
NULL);
for (i = 0; i < jniWorker->nArgs; i++) {
jstring arg = NULL;
if (jniWorker->args[i] != NULL) {
arg = (*jniEnv)->NewStringUTF(jniEnv, jniWorker->args[i]);
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.init() ARG %s\n", jniWorker->args[i]);
}
(*jniEnv)->SetObjectArrayElement(jniEnv, jargs, i, arg);
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.init() calling main()...\n");
(*jniEnv)->CallStaticVoidMethod(jniEnv,
jniWorker->jk_java_bridge_class,
jniWorker->jk_main_method, jargs);
}
else {
/* XXX Is it right thing to disable all non init hooks
* or make that customizable.
*/
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.init() disabling the non init hook worker\n");
_this->lb_disabled = JK_TRUE;
}
#if 0
vm->detach(env, vm);
#endif
/* XXX create a jni channel */
return JK_OK;
}
static int JK_METHOD jk2_jni_worker_destroy(jk_env_t *env, jk_bean_t *bean)
{
jk_worker_t *_this = bean->object;
jni_worker_data_t *jniWorker;
jk_vm_t *vm;
JNIEnv *jniEnv;
jstring cmd_line = NULL;
jstring stdout_name = NULL;
jstring stderr_name = NULL;
jclass jstringClass;
jarray jargs;
jstring arg = NULL;
if (!_this || !_this->worker_private) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"In destroy, assert failed - invalid parameters\n");
return JK_ERR;
}
vm = _this->workerEnv->vm;
if (vm == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"jni.destroy() No VM found\n");
return JK_ERR;
}
jniWorker = _this->worker_private;
if (jniWorker->hook < JK2_WORKER_HOOK_CLOSE) {
if (bean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.destroy(), done...worker is not hooked for close\n");
return JK_OK;
}
if (jniWorker->jk_java_bridge_class == NULL ||
jniWorker->jk_main_method == NULL) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.destroy(), done...worker does not have java methods\n");
return JK_OK;
}
if ((jniEnv = vm->attach(env, vm))) {
int i;
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.destroy(), shutting down Tomcat...\n");
jstringClass = (*jniEnv)->FindClass(jniEnv, "java/lang/String");
jargs =
(*jniEnv)->NewObjectArray(jniEnv, jniWorker->nArgs, jstringClass,
NULL);
for (i = 0; i < jniWorker->nArgs; i++) {
jstring arg = NULL;
if (jniWorker->args[i] != NULL) {
arg = (*jniEnv)->NewStringUTF(jniEnv, jniWorker->args[i]);
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.init() ARG %s\n", jniWorker->args[i]);
}
(*jniEnv)->SetObjectArrayElement(jniEnv, jargs, i, arg);
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.destroy() calling main()...\n");
jk_jni_status_code = 0;
(*jniEnv)->CallStaticVoidMethod(jniEnv,
jniWorker->jk_java_bridge_class,
jniWorker->jk_main_method, jargs);
if (jniWorker->hook == JK2_WORKER_HOOK_SHUTDOWN) {
#if APR_HAS_THREADS
while (jk_jni_status_code != 2) {
apr_thread_yield();
}
#endif
vm->detach(env, vm);
}
}
env->l->jkLog(env, env->l, JK_LOG_INFO, "jni.destroy() done\n");
return JK_OK;
}
int JK_METHOD jk2_worker_jni_factory(jk_env_t *env, jk_pool_t *pool,
jk_bean_t *result,
const char *type, const char *name)
{
jk_worker_t *_this;
jni_worker_data_t *jniData;
jk_workerEnv_t *wEnv;
if (name == NULL) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"jni.factory() NullPointerException name==null\n");
return JK_ERR;
}
wEnv = env->getByName(env, "workerEnv");
/* No singleton - you can have multiple jni workers,
running different bridges or starting different programs inprocess */
_this = (jk_worker_t *)pool->calloc(env, pool, sizeof(jk_worker_t));
jniData = (jni_worker_data_t *) pool->calloc(env, pool,
sizeof(jni_worker_data_t));
if (_this == NULL || jniData == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"jni.factory() OutOfMemoryException \n");
return JK_ERR;
}
_this->worker_private = jniData;
jniData->jk_java_bridge_class = NULL;
jniData->classNameOptions =
(char **)pool->calloc(env, pool, 4 * sizeof(char *));
jniData->args = pool->calloc(env, pool, 64 * sizeof(char *));
jniData->nArgs = 0;
jniData->stdout_name = NULL;
jniData->stderr_name = NULL;
result->init = jk2_jni_worker_init;
result->destroy = jk2_jni_worker_destroy;
_this->service = jk2_jni_worker_service;
result->object = _this;
result->setAttribute = jk2_jni_worker_setProperty;
_this->mbean = result;
_this->workerEnv = wEnv;
_this->workerEnv->addWorker(env, _this->workerEnv, _this);
return JK_OK;
}
#else
int JK_METHOD jk2_worker_jni_factory(jk_env_t *env, jk_pool_t *pool,
jk_bean_t *result,
const char *type, const char *name)
{
result->disabled = 1;
return JK_OK;
}
#endif