blob: 569e0e987f3144ee19072debfe568fa08b9837a5 [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.
*/
/**
* Channel using jni calls ( assuming in-process tomcat ).
*
* @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 "jk_map.h"
#include "jk_env.h"
#include "jk_channel.h"
#include "jk_global.h"
#include <string.h>
#include "jk_registry.h"
#include <jni.h>
/* default only, is configurable now */
#define JAVA_BRIDGE_CLASS_NAME ("org/apache/jk/apr/AprImpl")
#define JNI_TOMCAT_STARTED 2
extern int jk_jni_status_code;
/** Information specific for the socket channel
*/
typedef struct
{
jk_vm_t *vm;
char *className;
jclass jniBridge;
jmethodID writeMethod;
int status;
} jk_channel_jni_private_t;
typedef struct
{
JNIEnv *jniEnv;
int len;
jbyteArray jarray;
char *carray;
int arrayLen;
jobject jniJavaContext;
/* jobject msgJ; */
} jk_ch_jni_ep_private_t;
/*
Duplicate string and convert it to ASCII on EBDIC based systems
Needed for at least AS/400 and BS2000 but what about other EBDIC systems ?
Implement as macro cause:
we don't need to duplicate the strings if they are const on non EBDIC systems
*/
#if defined(AS400) || defined(_OSD_POSIX)
#define SSTRDUP_ASCII(e, s) ((e)->tmpPool->pstrdup2ascii(e, (e)->tmpPool, s))
#else
#define SSTRDUP_ASCII(e, s) (s)
#endif
static int JK_METHOD jk2_channel_jni_init(jk_env_t *env, jk_bean_t *jniWB)
{
jk_channel_t *jniW = jniWB->object;
jk_workerEnv_t *wEnv = jniW->workerEnv;
if (wEnv->childId != 0) {
if (jniW->worker != NULL)
jniW->worker->mbean->disabled = JK_TRUE;
return JK_ERR;
}
if (wEnv->vm == NULL) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channel_jni.init() no VM found\n");
if (jniW->worker != NULL) {
jniW->worker->mbean->disabled = JK_TRUE;
}
return JK_ERR;
}
return JK_OK;
}
/*
* Wait input event for timeout ms
*/
static int JK_METHOD jk2_channel_jni_hasinput(jk_env_t *env,
jk_channel_t *ch,
jk_endpoint_t *endpoint,
int timeout)
{
/*
* No delay in such case
*/
return (JK_TRUE);
}
/** Assume the jni-worker or someone else started
* tomcat and initialized what is needed.
*/
static int JK_METHOD jk2_channel_jni_open(jk_env_t *env,
jk_channel_t *_this,
jk_endpoint_t *endpoint)
{
jk_workerEnv_t *we = endpoint->worker->workerEnv;
JNIEnv *jniEnv;
jk_ch_jni_ep_private_t *epData;
jmethodID jmethod;
jobject jobj;
jstring jstr;
jk_channel_jni_private_t *jniCh = _this->_privatePtr;
if (endpoint->channelData != NULL) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channel_jni.open() already open, nothing else to do\n");
return JK_OK;
}
env->l->jkLog(env, env->l, JK_LOG_INFO, "channel_jni.open(): \n");
/* It is useless to continue if the channel worker
does not exist.
*/
if (_this->worker == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() NullPointerException, no channel worker found\n");
return JK_ERR;
}
jniCh->vm = (jk_vm_t *) we->vm;
if (jniCh->vm == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() no VM found\n");
_this->worker->mbean->disabled = JK_TRUE;
return JK_ERR;
}
jniEnv = (JNIEnv *) jniCh->vm->attach(env, jniCh->vm);
if (jniEnv == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() can't attach\n");
_this->worker->mbean->disabled = JK_TRUE;
return JK_ERR;
}
/* Create the buffers used by the write method. We allocate a
byte[] and jbyte[] - I have no idea what's more expensive,
to copy a buffer or to 'pin' the jbyte[] for copying.
This will be tuned if needed, for now it seems the easiest
solution
*/
epData = (jk_ch_jni_ep_private_t *)
endpoint->mbean->pool->calloc(env, endpoint->mbean->pool,
sizeof(jk_ch_jni_ep_private_t));
endpoint->channelData = epData;
/* AS400/BS2000 need EBCDIC to ASCII conversion for JNI */
jniCh->jniBridge =
(*jniEnv)->FindClass(jniEnv, SSTRDUP_ASCII(env, jniCh->className));
if (jniCh->jniBridge == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() can't find %s\n", jniCh->className);
_this->worker->mbean->disabled = JK_TRUE;
return JK_ERR;
}
jniCh->jniBridge = (*jniEnv)->NewGlobalRef(jniEnv, jniCh->jniBridge);
if (jniCh->jniBridge == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() Unable to allocate globalref for %s\n",
jniCh->className);
_this->worker->mbean->disabled = JK_TRUE;
return JK_ERR;
}
/* Interface to the callback mechansim. The idea is simple ( is it ? ) - we
use a similar pattern with java, trying to do as little as possible
in C and pass minimal information to allow this.
The pattern used for callback works for our message forwarding but also for
other things - like singnals, etc
*/
/* AS400/BS2000 need EBCDIC to ASCII conversion for JNI */
jmethod = (*jniEnv)->GetStaticMethodID(jniEnv, jniCh->jniBridge,
SSTRDUP_ASCII(env,
"createJavaContext"),
SSTRDUP_ASCII(env,
"(Ljava/lang/String;J)Ljava/lang/Object;"));
if (jmethod == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() can't find createJavaContext\n");
_this->worker->mbean->disabled = JK_TRUE;
if ((*jniEnv)->ExceptionCheck(jniEnv)) {
(*jniEnv)->ExceptionClear(jniEnv);
}
return JK_ERR;
}
/* AS400/BS2000 need EBCDIC to ASCII conversion for JNI */
jstr = (*jniEnv)->NewStringUTF(jniEnv, SSTRDUP_ASCII(env, "channelJni"));
jobj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jniCh->jniBridge,
jmethod,
jstr,
(jlong) (long)(void *)endpoint->
mbean);
if (jobj == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() Can't create java context\n");
epData->jniJavaContext = NULL;
_this->worker->mbean->disabled = JK_TRUE;
if ((*jniEnv)->ExceptionCheck(jniEnv)) {
(*jniEnv)->ExceptionClear(jniEnv);
}
return JK_ERR;
}
epData->jniJavaContext = (*jniEnv)->NewGlobalRef(jniEnv, jobj);
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channel_jni.open() Got ep %#lx %#lx\n", jobj,
epData->jniJavaContext);
/* XXX Destroy them in close */
/* AS400/BS2000 need EBCDIC to ASCII conversion for JNI */
jmethod = (*jniEnv)->GetStaticMethodID(jniEnv, jniCh->jniBridge,
SSTRDUP_ASCII(env, "getBuffer"),
SSTRDUP_ASCII(env,
"(Ljava/lang/Object;I)[B"));
if (jmethod == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.open() can't find getBuffer\n");
_this->worker->mbean->disabled = JK_TRUE;
return JK_ERR;
}
epData->jarray =
(*jniEnv)->CallStaticObjectMethod(jniEnv, jniCh->jniBridge, jmethod,
epData->jniJavaContext, 0);
epData->jarray = (*jniEnv)->NewGlobalRef(jniEnv, epData->jarray);
epData->arrayLen = (*jniEnv)->GetArrayLength(jniEnv, epData->jarray);
/* XXX > ajp buffer size. Don't know how to fragment or reallocate
yet */
epData->carray =
(char *)endpoint->mbean->pool->calloc(env, endpoint->mbean->pool,
epData->arrayLen);
/* AS400/BS2000 need EBCDIC to ASCII conversion for JNI */
jniCh->writeMethod =
(*jniEnv)->GetStaticMethodID(jniEnv, jniCh->jniBridge,
SSTRDUP_ASCII(env, "jniInvoke"),
SSTRDUP_ASCII(env,
"(JLjava/lang/Object;)I"));
if (jniCh->writeMethod == NULL) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"channel_jni.open() can't find jniInvoke\n");
_this->worker->mbean->disabled = JK_TRUE;
return JK_ERR;
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channel_jni.open() found write method, open ok\n");
_this->worker->mbean->disabled = JK_FALSE;
/* Don't detach ( XXX Need to find out when the thread is
* closing in order for this to work )
*/
/* jniCh->vm->detach( env, jniCh->vm ); */
return JK_OK;
}
/**
*/
static int JK_METHOD jk2_channel_jni_close(jk_env_t *env, jk_channel_t *_this,
jk_endpoint_t *endpoint)
{
jk_ch_jni_ep_private_t *epData;
JNIEnv *jniEnv;
jk_channel_jni_private_t *jniCh = _this->_privatePtr;
epData = (jk_ch_jni_ep_private_t *) endpoint->channelData;
if (epData == NULL) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channel_jni.close() no channel data\n");
return JK_ERR;
}
jniEnv = (JNIEnv *) jniCh->vm->attach(env, jniCh->vm);
if (jniEnv == NULL) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channel_jni.close() can't attach\n");
return JK_ERR;
}
if (epData->jarray != NULL) {
(*jniEnv)->DeleteGlobalRef(jniEnv, epData->jarray);
}
if (epData->jniJavaContext != NULL) {
(*jniEnv)->DeleteGlobalRef(jniEnv, epData->jniJavaContext);
}
jniCh->vm->detach(env, jniCh->vm);
env->l->jkLog(env, env->l, JK_LOG_INFO, "channel_jni.close() ok\n");
endpoint->channelData = NULL;
return JK_OK;
}
/** send a long message
* @param sd opened socket.
* @param b buffer containing the data.
* @param len length to send.
* @return -2: send returned 0 ? what this that ?
* -3: send failed.
* >0: total size send.
* @bug this fails on Unixes if len is too big for the underlying
* protocol.
* @was: jk_tcp_socket_sendfull
*/
static int JK_METHOD jk2_channel_jni_send(jk_env_t *env, jk_channel_t *_this,
jk_endpoint_t *endpoint,
jk_msg_t *msg)
{
/* int sd; */
int sent = 0;
char *b;
int len;
jbyte *nbuf;
jbyteArray jbuf;
int jlen = 0;
jboolean iscopy = 0;
JNIEnv *jniEnv;
jk_channel_jni_private_t *jniCh = _this->_privatePtr;
jk_ch_jni_ep_private_t *epData =
(jk_ch_jni_ep_private_t *) endpoint->channelData;
if (_this->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG, "channel_jni.send() %#lx\n",
epData);
if (epData == NULL) {
jk2_channel_jni_open(env, _this, endpoint);
epData = (jk_ch_jni_ep_private_t *) endpoint->channelData;
}
if (epData == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.send() error opening channel\n");
return JK_ERR;
}
if (epData->jniJavaContext == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channel_jni.send() no java context\n");
jk2_channel_jni_close(env, _this, endpoint);
return JK_ERR;
}
msg->end(env, msg);
len = msg->len;
b = msg->buf;
if (_this->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"channel_jni.send() (1) %#lx\n", epData);
jniEnv = NULL; /* epData->jniEnv; */
jbuf = epData->jarray;
if (jniCh->writeMethod == NULL) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"channel_jni.send() no write method\n");
return JK_ERR;
}
if (jniEnv == NULL) {
/* Try first getEnv, then attach */
jniEnv = (JNIEnv *) jniCh->vm->attach(env, jniCh->vm);
if (jniEnv == NULL) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channel_jni.send() can't attach\n");
return JK_ERR;
}
}
if (_this->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"channel_jni.send() getting byte array \n");
/* Copy the data in the ( recycled ) jbuf, then call the
* write method. XXX We could try 'pining' if the vm supports
* it, this is a looong lived object.
*/
#ifdef JK_JNI_CRITICAL
nbuf = (*jniEnv)->GetPrimitiveArrayCritical(jniEnv, jbuf, &iscopy);
#else
nbuf = (*jniEnv)->GetByteArrayElements(jniEnv, jbuf, &iscopy);
#endif
if (iscopy)
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channelJni.send() get java bytes iscopy %d\n", iscopy);
if (nbuf == NULL) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channelJni.send() Can't get java bytes");
return JK_ERR;
}
memcpy(nbuf, b, len);
#ifdef JK_JNI_CRITICAL
(*jniEnv)->ReleasePrimitiveArrayCritical(jniEnv, jbuf, nbuf, 0);
#else
(*jniEnv)->ReleaseByteArrayElements(jniEnv, jbuf, nbuf, 0);
#endif
if (_this->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"channel_jni.send() before send %#lx\n",
(void *)(long)epData->jniJavaContext);
sent = (*jniEnv)->CallStaticIntMethod(jniEnv,
jniCh->jniBridge,
jniCh->writeMethod,
(jlong) (long)(void *)env,
epData->jniJavaContext);
if (_this->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"channel_jni.send() result %d\n", sent);
return JK_OK;
}
/**
* Not used - we have a single thread, there is no 'blocking' - the
* java side will send messages by calling a native method, which will
* receive and dispatch.
*/
static int JK_METHOD jk2_channel_jni_recv(jk_env_t *env, jk_channel_t *_this,
jk_endpoint_t *endpoint,
jk_msg_t *msg)
{
/* jbyte *nbuf; */
/* jbyteArray jbuf; */
/* int jlen; */
/* jboolean iscommit; */
jk_channel_jni_private_t *jniCh = _this->_privatePtr;
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"channelJni.recv() method not supported for JNI channel\n");
return -1;
/* Old workaround:
nbuf=(jbyte *)endpoint->currentData;
if(nbuf==NULL ) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channelJni.recv() no jbyte[] was received\n");
return -1;
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"channelJni.recv() receiving %d\n", len);
memcpy( b, nbuf + endpoint->currentOffset, len );
endpoint->currentOffset += len;
return len;
*/
}
/** Called before request processing, to initialize resources.
All following calls will be in the same thread.
*/
int JK_METHOD jk2_channel_jni_beforeRequest(struct jk_env *env,
jk_channel_t *_this,
struct jk_worker *worker,
struct jk_endpoint *endpoint,
struct jk_ws_service *r)
{
jk_workerEnv_t *we = worker->workerEnv;
if (worker->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"service() attaching to vm\n");
return JK_OK;
}
/** Called after request processing. Used to be worker.done()
*/
int JK_METHOD jk2_channel_jni_afterRequest(struct jk_env *env,
jk_channel_t *_this,
struct jk_worker *worker,
struct jk_endpoint *endpoint,
struct jk_ws_service *r)
{
jk_workerEnv_t *we = worker->workerEnv;
if (we == NULL || we->vm == NULL) {
return JK_OK;
}
/*
* In case of not having the endpoint detach the jvm.
* XXX Remove calling this function from ajp13 worker?
*/
if (endpoint == NULL)
we->vm->detach(env, we->vm);
if (worker->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"channelJni.afterRequest() ok\n");
return JK_OK;
}
static int JK_METHOD jk2_channel_jni_setProperty(jk_env_t *env,
jk_bean_t *mbean,
char *name, void *valueP)
{
jk_channel_t *ch = (jk_channel_t *)mbean->object;
char *value = valueP;
jk_channel_jni_private_t *jniInfo =
(jk_channel_jni_private_t *) (ch->_privatePtr);
if (strcmp("class", name) == 0) {
jniInfo->className = value;
}
/* TODO: apache protocol hooks
else if( strcmp( "xxxx", name ) == 0 ) {
jniInfo->xxxx=value;
}
*/
else {
return jk2_channel_setAttribute(env, mbean, name, valueP);
}
return JK_OK;
}
/** Called by java. Will take the msg and dispatch it to workerEnv, as if it would
* be if received via socket
*/
int JK_METHOD jk2_channel_jni_invoke(jk_env_t *env, jk_bean_t *bean,
jk_endpoint_t *ep, int code,
jk_msg_t *msg, int raw)
{
jk_channel_t *ch = (jk_channel_t *)bean->object;
int rc = JK_OK;
if (ch->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG, "ch.%d() \n", code);
code = (int)msg->getByte(env, msg);
if (ch->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"channelJni.java2cInvoke() %d\n", code);
return ep->worker->workerEnv->dispatch(env, ep->worker->workerEnv,
ep->currentRequest, ep, code,
ep->reply);
}
static int JK_METHOD jk2_channel_jni_status(jk_env_t *env,
struct jk_worker *worker,
jk_channel_t *_this)
{
jk_channel_jni_private_t *jniCh = _this->_privatePtr;
if (jniCh->status != JNI_TOMCAT_STARTED) {
jniCh->status = jk_jni_status_code;
if (jniCh->status != JNI_TOMCAT_STARTED)
return JK_ERR;
}
return JK_OK;
}
int JK_METHOD jk2_channel_jni_factory(jk_env_t *env, jk_pool_t *pool,
jk_bean_t *result,
const char *type, const char *name)
{
jk_channel_t *ch = result->object;
jk_workerEnv_t *wEnv;
jk_channel_jni_private_t *jniPrivate;
wEnv = env->getByName(env, "workerEnv");
ch = (jk_channel_t *)pool->calloc(env, pool, sizeof(jk_channel_t));
ch->recv = jk2_channel_jni_recv;
ch->send = jk2_channel_jni_send;
ch->open = jk2_channel_jni_open;
ch->close = jk2_channel_jni_close;
ch->hasinput = jk2_channel_jni_hasinput;
ch->beforeRequest = jk2_channel_jni_beforeRequest;
ch->afterRequest = jk2_channel_jni_afterRequest;
ch->status = jk2_channel_jni_status;
ch->_privatePtr = jniPrivate =
(jk_channel_jni_private_t *) pool->calloc(env, pool,
sizeof
(jk_channel_jni_private_t));
jniPrivate->className = JAVA_BRIDGE_CLASS_NAME;
ch->is_stream = JK_FALSE;
/* No special attribute */
result->setAttribute = jk2_channel_jni_setProperty;
ch->mbean = result;
result->object = ch;
result->init = jk2_channel_jni_init;
ch->workerEnv = wEnv;
wEnv->addChannel(env, wEnv, ch);
result->invoke = jk2_channel_jni_invoke;
return JK_OK;
}
#else
int JK_METHOD jk2_channel_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