/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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.
 */

/***************************************************************************
 * Description: In process JNI worker                                      *
 * Author:      Gal Shachor <shachor@il.ibm.com>                           *
 * Based on:                                                               *
 * Version:     $Revision: 750428 $                                           *
 ***************************************************************************/

#if !defined(WIN32) && !defined(NETWARE) && !defined(AS400)
#include <dlfcn.h>
#endif

#include <jni.h>

#include "jk_pool.h"
#include "jk_jni_worker.h"
#include "jk_util.h"

#if !defined(JNI_VERSION_1_6)
#if defined LINUX && defined APACHE2_SIGHACK
#include <pthread.h>
#include <signal.h>
#include <bits/signum.h>
#endif

#ifdef NETWARE
#ifdef __NOVELL_LIBC__
#include <dlfcn.h>
#else
#include <nwthread.h>
#include <nwadv.h>
#endif
#endif

#ifndef JNI_VERSION_1_1
#define JNI_VERSION_1_1 0x00010001
#endif

/* probably on an older system that doesn't support RTLD_NOW or RTLD_LAZY.
 * The below define is a lie since we are really doing RTLD_LAZY since the
 * system doesn't support RTLD_NOW.
 */
#ifndef RTLD_NOW
#define RTLD_NOW 1
#endif

#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL 0
#endif

#define null_check(e) if ((e) == 0) return JK_FALSE

jint(JNICALL * jni_get_default_java_vm_init_args) (void *) = NULL;
jint(JNICALL * jni_create_java_vm) (JavaVM **, JNIEnv **, void *) = NULL;
#ifdef AS400
jint(JNICALL * jni_get_created_java_vms) (JavaVM **, long, long *) = NULL;
#else
jint(JNICALL * jni_get_created_java_vms) (JavaVM **, int, int *) = NULL;
#endif

#define TC33_JAVA_BRIDGE_CLASS_NAME ("org/apache/tomcat/modules/server/JNIEndpoint")
#define TC32_JAVA_BRIDGE_CLASS_NAME ("org/apache/tomcat/service/JNIEndpoint")

static jk_worker_t *the_singleton_jni_worker = NULL;

struct jni_worker
{

    int was_verified;
    int was_initialized;

    jk_pool_t p;
    jk_pool_atom_t buf[TINY_POOL_SIZE];

    /*
     * JVM Object pointer.
     */
    JavaVM *jvm;

    /*
     * [V] JNIEnv used for boostraping from validate -> init w/o an attach
     */
    JNIEnv *tmp_env;

    /*
     * Web Server to Java bridge, instance and class.
     */
    jobject jk_java_bridge_object;
    jclass jk_java_bridge_class;

    /*
     * Java methods ids, to jump into the JVM
     */
    jmethodID jk_startup_method;
    jmethodID jk_service_method;
    jmethodID jk_shutdown_method;

    /*
     * Command line for tomcat startup
     */
    char *tomcat_cmd_line;

    /*
     * Bridge Type, Tomcat 32/33/40/41/5
     */
    unsigned bridge_type;

    /*
     * Classpath
     */
    char *tomcat_classpath;

    /*
     * Full path to the jni javai/jvm dll
     */
    char *jvm_dll_path;

    /*
     * Initial Java heap size
     */
    unsigned tomcat_ms;

    /*
     * Max Java heap size
     */
    unsigned tomcat_mx;

    /*
     * Java system properties
     */
    char **sysprops;

#ifdef JNI_VERSION_1_2
    /*
     * Java 2 initialization options (-X... , -verbose etc.)
     */
    char **java2opts;

    /*
     * Java 2 lax/strict option checking (bool)
     */
    int java2lax;
#endif

    /*
     * stdout and stderr file names for Java
     */
    char *stdout_name;
    char *stderr_name;

    char *name;
    jk_worker_t worker;
};
typedef struct jni_worker jni_worker_t;

struct jni_endpoint
{
    int attached;
    JNIEnv *env;
    jni_worker_t *worker;

    jk_endpoint_t endpoint;
};
typedef struct jni_endpoint jni_endpoint_t;


static int load_jvm_dll(jni_worker_t * p, jk_logger_t *l);

static int open_jvm(jni_worker_t * p, JNIEnv ** env, jk_logger_t *l);

static int open_jvm1(jni_worker_t * p, JNIEnv ** env, jk_logger_t *l);

#ifdef JNI_VERSION_1_2
static int detect_jvm_version(jk_logger_t *l);

static int open_jvm2(jni_worker_t * p, JNIEnv ** env, jk_logger_t *l);
#endif


static int get_bridge_object(jni_worker_t * p, JNIEnv * env, jk_logger_t *l);

static int get_method_ids(jni_worker_t * p, JNIEnv * env, jk_logger_t *l);

static JNIEnv *attach_to_jvm(jni_worker_t * p, jk_logger_t *l);

static void detach_from_jvm(jni_worker_t * p, jk_logger_t *l);


/*
   Duplicate string and convert it to ASCII on EBDIC based systems
   Needed for at least AS/400 (before V5R4 ?) and BS2000 but what about other EBDIC systems ?
*/
static void *strdup_ascii(jk_pool_t *p, char *s)
{
    char *rc;
    rc = jk_pool_strdup(p, s);

#if defined(AS400) || defined(_OSD_POSIX)
    jk_xlate_to_ascii(rc, strlen(rc));
#endif

    return rc;
}

#if defined LINUX && defined APACHE2_SIGHACK
static void linux_signal_hack()
{
    sigset_t newM;
    sigset_t old;

    sigemptyset(&newM);
    pthread_sigmask(SIG_SETMASK, &newM, &old);

    sigdelset(&old, SIGUSR1);
    sigdelset(&old, SIGUSR2);
    sigdelset(&old, SIGUNUSED);
    sigdelset(&old, SIGRTMIN);
    sigdelset(&old, SIGRTMIN + 1);
    sigdelset(&old, SIGRTMIN + 2);
    pthread_sigmask(SIG_SETMASK, &old, NULL);
}

static void print_signals(sigset_t * sset)
{
    int sig;
    for (sig = 1; sig < 20; sig++) {
        if (sigismember(sset, sig)) {
            printf(" %d", sig);
        }
    }
    printf("\n");
}
#endif

/*
 * Return values of service() method for jni worker:
 * return value  is_error              reason
 * JK_FALSE      JK_HTTP_SERVER_ERROR  Invalid parameters (null values)
 *                                     Error during attach to the JNI backend
 *                                     Error during JNI call
 * JK_TRUE       JK_HTTP_OK            All other cases
 */
static int JK_METHOD service(jk_endpoint_t *e,
                             jk_ws_service_t *s,
                             jk_logger_t *l, int *is_error)
{
    jni_endpoint_t *p;
    jint rc;

    JK_TRACE_ENTER(l);

    if (!e || !e->endpoint_private || !s || !is_error) {
        JK_LOG_NULL_PARAMS(l);
        if (is_error)
            *is_error = JK_HTTP_SERVER_ERROR;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p = e->endpoint_private;

    /* Set returned error to OK */
    *is_error = JK_HTTP_OK;

    if (!p->attached) {
        /* Try to attach */
        if (!(p->env = attach_to_jvm(p->worker, l))) {
            jk_log(l, JK_LOG_EMERG, "Attach failed");
            *is_error = JK_HTTP_SERVER_ERROR;
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }
        p->attached = JK_TRUE;
    }

    /* we are attached now */

    /*
     * When we call the JVM we cannot know what happens
     * So we can not recover !!!
     */
    jk_log(l, JK_LOG_DEBUG, "In service, calling Tomcat...");

    rc = (*(p->env))->CallIntMethod(p->env,
                                    p->worker->jk_java_bridge_object,
                                    p->worker->jk_service_method,
                                    /* [V] For some reason gcc likes this pointer -> int -> jlong conversion, */
                                    /*     but not the direct pointer -> jlong conversion. I hope it's okay.  */
#ifdef AS400
                                    s, l
#else
                                    (jlong) (int)s, (jlong) (int)l
#endif
        );

    /* [V] Righ now JNIEndpoint::service() only returns 1 or 0 */
    if (rc) {
        jk_log(l, JK_LOG_DEBUG, "Tomcat returned OK, done");
        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }
    else {
        jk_log(l, JK_LOG_ERROR, "Tomcat FAILED!");
        *is_error = JK_HTTP_SERVER_ERROR;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
}

static int JK_METHOD done(jk_endpoint_t **e, jk_logger_t *l)
{
    jni_endpoint_t *p;

    JK_TRACE_ENTER(l);
    if (!e || !*e || !(*e)->endpoint_private) {
        JK_LOG_NULL_PARAMS(l);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p = (*e)->endpoint_private;

    if (p->attached) {
        detach_from_jvm(p->worker, l);
    }

    free(p);
    *e = NULL;
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

static int JK_METHOD validate(jk_worker_t *pThis,
                              jk_map_t *props,
                              jk_worker_env_t *we, jk_logger_t *l)
{
    jni_worker_t *p;
    int mem_config = 0;
    int btype = 0;
    const char *str_config = NULL;
    JNIEnv *env;

    JK_TRACE_ENTER(l);

    if (!pThis || !pThis->worker_private) {
        JK_LOG_NULL_PARAMS(l);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p = pThis->worker_private;

    if (p->was_verified) {
        jk_log(l, JK_LOG_DEBUG, "been here before, done");
        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }

    if (jk_get_worker_mx(props, p->name, (unsigned int *)&mem_config)) {
        p->tomcat_mx = mem_config;
    }

    if (jk_get_worker_ms(props, p->name, (unsigned int *)&mem_config)) {
        p->tomcat_ms = mem_config;
    }

    if (jk_get_worker_classpath(props, p->name, &str_config)) {
        p->tomcat_classpath = jk_pool_strdup(&p->p, str_config);
    }

    if (!p->tomcat_classpath) {
        jk_log(l, JK_LOG_EMERG, "no classpath");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (jk_get_worker_bridge_type(props, p->name, (unsigned int *)&btype)) {
        p->bridge_type = btype;
    }

    if (jk_get_worker_jvm_path(props, p->name, &str_config)) {
        p->jvm_dll_path = jk_pool_strdup(&p->p, str_config);
    }

    if (!p->jvm_dll_path || !jk_file_exists(p->jvm_dll_path)) {
        jk_log(l, JK_LOG_EMERG, "no jvm_dll_path");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (jk_get_worker_cmd_line(props, p->name, &str_config)) {
        p->tomcat_cmd_line = jk_pool_strdup(&p->p, str_config);
    }

    if (jk_get_worker_stdout(props, p->name, &str_config)) {
        p->stdout_name = jk_pool_strdup(&p->p, str_config);
    }

    if (jk_get_worker_stderr(props, p->name, &str_config)) {
        p->stderr_name = jk_pool_strdup(&p->p, str_config);
    }

    if (jk_get_worker_sysprops(props, p->name, &str_config)) {
        p->sysprops = jk_parse_sysprops(&p->p, str_config);
    }

#ifdef JNI_VERSION_1_2
    if (jk_get_worker_str_prop(props, p->name, "java2opts", &str_config)) {
        /* jk_log(l, JK_LOG_DEBUG, "Got opts: %s", str_config); */
        p->java2opts = jk_parse_sysprops(&p->p, str_config);
    }
    if (jk_get_worker_int_prop(props, p->name, "java2lax", &mem_config)) {
        p->java2lax = mem_config ? JK_TRUE : JK_FALSE;
    }
#endif

    if (jk_get_worker_libpath(props, p->name, &str_config)) {
        jk_append_libpath(&p->p, str_config);
    }

    if (!load_jvm_dll(p, l)) {
        jk_log(l, JK_LOG_EMERG, "can't load jvm dll");
        /* [V] no detach needed here */
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (!open_jvm(p, &env, l)) {
        jk_log(l, JK_LOG_EMERG, "can't open jvm");
        /* [V] no detach needed here */
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (!get_bridge_object(p, env, l)) {
        jk_log(l, JK_LOG_EMERG, "can't get bridge object");
        /* [V] the detach here may segfault on 1.1 JVM... */
        detach_from_jvm(p, l);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (!get_method_ids(p, env, l)) {
        jk_log(l, JK_LOG_EMERG, "can't get method ids");
        /* [V] the detach here may segfault on 1.1 JVM... */
        detach_from_jvm(p, l);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p->was_verified = JK_TRUE;
    p->tmp_env = env;

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

static int JK_METHOD init(jk_worker_t *pThis,
                          jk_map_t *props,
                          jk_worker_env_t *we, jk_logger_t *l)
{
    jni_worker_t *p;
    JNIEnv *env;

    JK_TRACE_ENTER(l);

    if (!pThis || !pThis->worker_private) {
        JK_LOG_NULL_PARAMS(l);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p = pThis->worker_private;

    if (p->was_initialized) {
        jk_log(l, JK_LOG_DEBUG, "done (been here!)");
        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }

    if (!p->jvm ||
        !p->jk_java_bridge_object ||
        !p->jk_service_method ||
        !p->jk_startup_method || !p->jk_shutdown_method) {
        jk_log(l, JK_LOG_EMERG, "worker not set completely");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    /* [V] init is called from the same thread that called validate */
    /* there is no need to attach to the JVM, just get the env */

    /* if(env = attach_to_jvm(p,l)) { */
    if ((env = p->tmp_env)) {
        jstring cmd_line = (jstring) NULL;
        jstring stdout_name = (jstring) NULL;
        jstring stderr_name = (jstring) NULL;
        jint rc = 0;

        /* AS400/BS2000 need EBCDIC to ASCII conversion for JNI */

        if (p->tomcat_cmd_line) {
            cmd_line =
                (*env)->NewStringUTF(env,
                                     strdup_ascii(&p->p, p->tomcat_cmd_line));
        }
        if (p->stdout_name) {
            stdout_name =
                (*env)->NewStringUTF(env,
                                     strdup_ascii(&p->p, p->stdout_name));
        }
        if (p->stderr_name) {
            stderr_name =
                (*env)->NewStringUTF(env,
                                     strdup_ascii(&p->p, p->stderr_name));
        }

        jk_log(l, JK_LOG_DEBUG,
               "calling Tomcat to intialize itself...");
        rc = (*env)->CallIntMethod(env, p->jk_java_bridge_object,
                                   p->jk_startup_method, cmd_line,
                                   stdout_name, stderr_name);

        detach_from_jvm(p, l);

        if (rc) {
            p->was_initialized = JK_TRUE;
            jk_log(l, JK_LOG_DEBUG, "Tomcat initialized OK, done");
            JK_TRACE_EXIT(l);
            return JK_TRUE;
        }
        else {
            jk_log(l, JK_LOG_EMERG, "could not initialize Tomcat");
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }
    }
    else {
        jk_log(l, JK_LOG_ERROR,
               "In init, FIXME: init didn't gen env from validate!");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
}

static int JK_METHOD get_endpoint(jk_worker_t *pThis,
                                  jk_endpoint_t **pend, jk_logger_t *l)
{
    /* [V] This slow, needs replacement */
    jni_endpoint_t *p;

    JK_TRACE_ENTER(l);

    if (!pThis || !pThis->worker_private || !pend) {
        JK_LOG_NULL_PARAMS(l);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p = (jni_endpoint_t *) calloc(1, sizeof(jni_endpoint_t));
    if (p) {
        p->attached = JK_FALSE;
        p->env = NULL;
        p->worker = pThis->worker_private;
        p->endpoint.endpoint_private = p;
        p->endpoint.service = service;
        p->endpoint.done = done;
        *pend = &p->endpoint;

        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }
    else {
        jk_log(l, JK_LOG_ERROR,
               "could not allocate endpoint");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
}

static int JK_METHOD destroy(jk_worker_t **pThis, jk_logger_t *l)
{
    jni_worker_t *p;
    JNIEnv *env;

    JK_TRACE_ENTER(l);

    if (!pThis || !*pThis || !(*pThis)->worker_private) {
        JK_LOG_NULL_PARAMS(l);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p = (*pThis)->worker_private;

    if (!p->jvm) {
        jk_log(l, JK_LOG_DEBUG, "JVM not intantiated");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (!p->jk_java_bridge_object || !p->jk_shutdown_method) {
        jk_log(l, JK_LOG_DEBUG, "Tomcat not intantiated");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if ((env = attach_to_jvm(p, l))) {
        jk_log(l, JK_LOG_DEBUG, "shutting down Tomcat...");
        (*env)->CallVoidMethod(env,
                               p->jk_java_bridge_object,
                               p->jk_shutdown_method);
        detach_from_jvm(p, l);
    }

    jk_close_pool(&p->p);
    free(p);

    jk_log(l, JK_LOG_DEBUG, "destroyed");

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

int JK_METHOD jni_worker_factory(jk_worker_t **w,
                                 const char *name, jk_logger_t *l)
{
    jni_worker_t *private_data;

    JK_TRACE_ENTER(l);

    jk_log(l, JK_LOG_WARNING,
           "Worker '%s' is of type jni, which is deprecated "
           "and will be removed in a future release.",
           name ? name : "(null)");

    if (!name || !w) {
        JK_LOG_NULL_PARAMS(l);
        JK_TRACE_EXIT(l);
        return 0;
    }

    if (the_singleton_jni_worker) {
        jk_log(l, JK_LOG_DEBUG,
               "instance already created");
        *w = the_singleton_jni_worker;
        JK_TRACE_EXIT(l);
        return JK_JNI_WORKER_TYPE;
    }

    private_data = (jni_worker_t *) malloc(sizeof(jni_worker_t));

    if (!private_data) {
        jk_log(l, JK_LOG_ERROR,
               "memory allocation error");
        JK_TRACE_EXIT(l);
        return 0;
    }

    jk_open_pool(&private_data->p,
                 private_data->buf, sizeof(jk_pool_atom_t) * TINY_POOL_SIZE);

    private_data->name = jk_pool_strdup(&private_data->p, name);

    if (!private_data->name) {
        jk_log(l, JK_LOG_ERROR,
               "memory allocation error");
        jk_close_pool(&private_data->p);
        free(private_data);
        JK_TRACE_EXIT(l);
        return 0;
    }

    private_data->was_verified = JK_FALSE;
    private_data->was_initialized = JK_FALSE;
    private_data->jvm = NULL;
    private_data->tmp_env = NULL;
    private_data->jk_java_bridge_object = (jobject) NULL;
    private_data->jk_java_bridge_class = (jclass) NULL;
    private_data->jk_startup_method = (jmethodID) NULL;
    private_data->jk_service_method = (jmethodID) NULL;
    private_data->jk_shutdown_method = (jmethodID) NULL;
    private_data->tomcat_cmd_line = NULL;
    private_data->tomcat_classpath = NULL;
    private_data->bridge_type = TC33_BRIDGE_TYPE;
    private_data->jvm_dll_path = NULL;
    private_data->tomcat_ms = 0;
    private_data->tomcat_mx = 0;
    private_data->sysprops = NULL;
#ifdef JNI_VERSION_1_2
    private_data->java2opts = NULL;
    private_data->java2lax = JK_TRUE;
#endif
    private_data->stdout_name = NULL;
    private_data->stderr_name = NULL;

    private_data->worker.worker_private = private_data;
    private_data->worker.validate = validate;
    private_data->worker.init = init;
    private_data->worker.get_endpoint = get_endpoint;
    private_data->worker.destroy = destroy;

    *w = &private_data->worker;
    the_singleton_jni_worker = &private_data->worker;

    JK_TRACE_EXIT(l);
    return JK_JNI_WORKER_TYPE;
}

static int load_jvm_dll(jni_worker_t * p, jk_logger_t *l)
{
#ifdef WIN32
    HINSTANCE hInst = LoadLibrary(p->jvm_dll_path);
    if (hInst) {
        (FARPROC) jni_create_java_vm =
            GetProcAddress(hInst, "JNI_CreateJavaVM");

        (FARPROC) jni_get_created_java_vms =
            GetProcAddress(hInst, "JNI_GetCreatedJavaVMs");

        (FARPROC) jni_get_default_java_vm_init_args =
            GetProcAddress(hInst, "JNI_GetDefaultJavaVMInitArgs");

        jk_log(l, JK_LOG_DEBUG, "Loaded all JNI procs");

        if (jni_create_java_vm && jni_get_default_java_vm_init_args
            && jni_get_created_java_vms) {
            return JK_TRUE;
        }

        FreeLibrary(hInst);
    }
#elif defined(NETWARE) && !defined(__NOVELL_LIBC__)
    int javaNlmHandle = FindNLMHandle("JVM");
    if (0 == javaNlmHandle) {
        /* if we didn't get a handle, try to load java and retry getting the */
        /* handle */
        spawnlp(P_NOWAIT, "JVM.NLM", NULL);
        ThreadSwitchWithDelay();
        javaNlmHandle = FindNLMHandle("JVM");
        if (0 == javaNlmHandle)
            printf("Error loading Java.");

    }
    if (0 != javaNlmHandle) {
        jni_create_java_vm = ImportSymbol(GetNLMHandle(), "JNI_CreateJavaVM");
        jni_get_created_java_vms =
            ImportSymbol(GetNLMHandle(), "JNI_GetCreatedJavaVMs");
        jni_get_default_java_vm_init_args =
            ImportSymbol(GetNLMHandle(), "JNI_GetDefaultJavaVMInitArgs");
    }
    if (jni_create_java_vm && jni_get_default_java_vm_init_args
        && jni_get_created_java_vms) {
        return JK_TRUE;
    }
#elif defined(AS400)
    jk_log(l, JK_LOG_DEBUG,
           "Direct reference to JNI entry points (no SRVPGM)");
    jni_create_java_vm = &JNI_CreateJavaVM;
    jni_get_default_java_vm_init_args = &JNI_GetDefaultJavaVMInitArgs;
    jni_get_created_java_vms = &JNI_GetCreatedJavaVMs;
#else
    void *handle;
    jk_log(l, JK_LOG_DEBUG, "loading JVM %s", p->jvm_dll_path);

    handle = dlopen(p->jvm_dll_path, RTLD_NOW | RTLD_GLOBAL);

    if (!handle) {
        jk_log(l, JK_LOG_EMERG,
               "Can't load native library %s : %s", p->jvm_dll_path,
               dlerror());
    }
    else {
        jni_create_java_vm = dlsym(handle, "JNI_CreateJavaVM");
        jni_get_default_java_vm_init_args =
            dlsym(handle, "JNI_GetDefaultJavaVMInitArgs");
        jni_get_created_java_vms = dlsym(handle, "JNI_GetCreatedJavaVMs");

        if (jni_create_java_vm && jni_get_default_java_vm_init_args &&
            jni_get_created_java_vms) {
            jk_log(l, JK_LOG_DEBUG,
                   "In load_jvm_dll, symbols resolved, done");
            return JK_TRUE;
        }
        jk_log(l, JK_LOG_EMERG,
               "Can't resolve JNI_CreateJavaVM or JNI_GetDefaultJavaVMInitArgs");
        dlclose(handle);
    }
#endif
    return JK_FALSE;
}

static int open_jvm(jni_worker_t * p, JNIEnv ** env, jk_logger_t *l)
{
#ifdef JNI_VERSION_1_2
    int jvm_version = detect_jvm_version(l);

    switch (jvm_version) {
    case JNI_VERSION_1_1:
        return open_jvm1(p, env, l);
    case JNI_VERSION_1_2:
        return open_jvm2(p, env, l);
    default:
        return JK_FALSE;
    }
#else
    /* [V] Make sure this is _really_ visible */
#warning -------------------------------------------------------
#warning NO JAVA 2 HEADERS! SUPPORT FOR JAVA 2 FEATURES DISABLED
#warning -------------------------------------------------------
    return open_jvm1(p, env, l);
#endif
}

static int open_jvm1(jni_worker_t * p, JNIEnv ** env, jk_logger_t *l)
{
    JDK1_1InitArgs vm_args;
    JNIEnv *penv;
    int err;
    *env = NULL;

    JK_TRACE_ENTER(l);

    vm_args.version = JNI_VERSION_1_1;

    if (0 != jni_get_default_java_vm_init_args(&vm_args)) {
        jk_log(l, JK_LOG_EMERG, "can't get default vm init args");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_log(l, JK_LOG_DEBUG, "got default jvm args");

    if (vm_args.classpath) {
        size_t len = strlen(vm_args.classpath) +
            strlen(p->tomcat_classpath) + 3;
        char *tmp = jk_pool_alloc(&p->p, len);
        if (tmp) {
            sprintf(tmp, "%s%c%s",
                    strdup_ascii(&p->p, p->tomcat_classpath),
                    PATH_SEPERATOR, vm_args.classpath);
            p->tomcat_classpath = tmp;
        }
        else {
            jk_log(l, JK_LOG_EMERG,
                   "allocation error for classpath");
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }
    }
    vm_args.classpath = p->tomcat_classpath;

    if (p->tomcat_mx) {
        vm_args.maxHeapSize = p->tomcat_mx;
    }

    if (p->tomcat_ms) {
        vm_args.minHeapSize = p->tomcat_ms;
    }

    if (p->sysprops) {
        /* No EBCDIC to ASCII conversion here for AS/400 - later */
        vm_args.properties = p->sysprops;
    }

    jk_log(l, JK_LOG_DEBUG, "In open_jvm1, about to create JVM...");
    if ((err = jni_create_java_vm(&(p->jvm), &penv, &vm_args)) != 0) {
        jk_log(l, JK_LOG_EMERG,
               "could not create JVM, code: %d ", err);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_log(l, JK_LOG_DEBUG, "JVM created, done");

    *env = penv;

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

#ifdef JNI_VERSION_1_2
static int detect_jvm_version(jk_logger_t *l)
{
    JNIEnv *env = NULL;
    JDK1_1InitArgs vm_args;

    JK_TRACE_ENTER(l);

    /* [V] Idea: ask for 1.2. If the JVM is 1.1 it will return 1.1 instead  */
    /*     Note: asking for 1.1 won't work, 'cause 1.2 JVMs will return 1.1 */
    vm_args.version = JNI_VERSION_1_2;

    if (0 != jni_get_default_java_vm_init_args(&vm_args)) {
        jk_log(l, JK_LOG_EMERG, "can't get default vm init args");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_log(l, JK_LOG_DEBUG,
           "found version: %X, done", vm_args.version);

    JK_TRACE_EXIT(l);
    return vm_args.version;
}

static char *build_opt_str(jk_pool_t *p,
                           char *opt_name, char *opt_value, jk_logger_t *l)
{
    size_t len = strlen(opt_name) + strlen(opt_value) + 2;

    /* [V] IMHO, these should not be deallocated as long as the JVM runs */
    char *tmp = jk_pool_alloc(p, len);

    if (tmp) {
        sprintf(tmp, "%s%s", opt_name, opt_value);
        return tmp;
    }
    else {
        jk_log(l, JK_LOG_EMERG,
               "allocation error for %s", opt_name);
        return NULL;
    }
}

static char *build_opt_int(jk_pool_t *p,
                           char *opt_name, int opt_value, jk_logger_t *l)
{
    /* [V] this should suffice even for 64-bit int */
    size_t len = strlen(opt_name) + 20 + 2;
    /* [V] IMHO, these should not be deallocated as long as the JVM runs */
    char *tmp = jk_pool_alloc(p, len);

    if (tmp) {
        sprintf(tmp, "%s%d", opt_name, opt_value);
        return tmp;
    }
    else {
        jk_log(l, JK_LOG_EMERG,
               "allocation error for %s", opt_name);
        return NULL;
    }
}

static int open_jvm2(jni_worker_t * p, JNIEnv ** env, jk_logger_t *l)
{
    JavaVMInitArgs vm_args;
    JNIEnv *penv = NULL;
    JavaVMOption options[100];
    int optn = 0, err;
    char *tmp;

    *env = NULL;

    JK_TRACE_ENTER(l);

    vm_args.version = JNI_VERSION_1_2;
    vm_args.options = options;

/* AS/400 need EBCDIC to ASCII conversion to parameters passed to JNI */
/* No conversion for ASCII based systems (what about BS2000 ?) */

    if (p->tomcat_classpath) {
        jk_log(l, JK_LOG_DEBUG, "setting classpath to %s",
               p->tomcat_classpath);
        tmp =
            build_opt_str(&p->p, "-Djava.class.path=", p->tomcat_classpath,
                          l);
        null_check(tmp);
        options[optn++].optionString = strdup_ascii(&p->p, tmp);
    }

    if (p->tomcat_mx) {
        jk_log(l, JK_LOG_DEBUG, "setting max heap to %d",
               p->tomcat_mx);
        tmp = build_opt_int(&p->p, "-Xmx", p->tomcat_mx, l);
        null_check(tmp);
        options[optn++].optionString = strdup_ascii(&p->p, tmp);
    }

    if (p->tomcat_ms) {
        jk_log(l, JK_LOG_DEBUG, "setting start heap to %d",
               p->tomcat_ms);
        tmp = build_opt_int(&p->p, "-Xms", p->tomcat_ms, l);
        null_check(tmp);
        options[optn++].optionString = strdup_ascii(&p->p, tmp);
    }

    if (p->sysprops) {
        int i = 0;
        while (p->sysprops[i]) {
            jk_log(l, JK_LOG_DEBUG, "setting %s",
                   p->sysprops[i]);
            tmp = build_opt_str(&p->p, "-D", p->sysprops[i], l);
            null_check(tmp);
            options[optn++].optionString = strdup_ascii(&p->p, tmp);
            i++;
        }
    }

    if (p->java2opts) {
        int i = 0;

        while (p->java2opts[i]) {
            jk_log(l, JK_LOG_DEBUG, "using option: %s",
                   p->java2opts[i]);
            /* Pass it "as is" */
            options[optn++].optionString =
                strdup_ascii(&p->p, p->java2opts[i++]);
        }
    }

    vm_args.nOptions = optn;

    if (p->java2lax) {
        jk_log(l, JK_LOG_DEBUG,
               "the JVM will ignore unknown options");
        vm_args.ignoreUnrecognized = JNI_TRUE;
    }
    else {
        jk_log(l, JK_LOG_DEBUG,
               "the JVM will FAIL if it finds unknown options");
        vm_args.ignoreUnrecognized = JNI_FALSE;
    }

    jk_log(l, JK_LOG_DEBUG, "about to create JVM...");

    err = jni_create_java_vm(&(p->jvm), &penv, &vm_args);

    if (JNI_EEXIST == err) {
#ifdef AS400
        long vmCount;
#else
        int vmCount;
#endif
        jk_log(l, JK_LOG_DEBUG, "JVM alread instantiated."
               "Trying to attach instead.");

        jni_get_created_java_vms(&(p->jvm), 1, &vmCount);
        if (NULL != p->jvm)
            penv = attach_to_jvm(p, l);

        if (NULL != penv)
            err = 0;
    }

    if (err != 0) {
        jk_log(l, JK_LOG_EMERG, "Fail-> could not create JVM, code: %d ",
               err);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    *env = penv;
    jk_log(l, JK_LOG_DEBUG, "JVM created");

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}
#endif

static int get_bridge_object(jni_worker_t * p, JNIEnv * env, jk_logger_t *l)
{
    char *btype;
    char *ctype;

    jmethodID constructor_method_id;

    JK_TRACE_ENTER(l);

    switch (p->bridge_type) {
    case TC32_BRIDGE_TYPE:
        btype = TC32_JAVA_BRIDGE_CLASS_NAME;
        break;

    case TC33_BRIDGE_TYPE:
        btype = TC33_JAVA_BRIDGE_CLASS_NAME;
        break;

    case TC40_BRIDGE_TYPE:
    case TC41_BRIDGE_TYPE:
    case TC50_BRIDGE_TYPE:
        jk_log(l, JK_LOG_EMERG, "Bridge type %d not supported",
               p->bridge_type);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

/* AS400/BS2000 need conversion from EBCDIC to ASCII before passing to JNI */
/* for others, strdup_ascii is just jk_pool_strdup */

    ctype = strdup_ascii(&p->p, btype);

    p->jk_java_bridge_class = (*env)->FindClass(env, ctype);

    if (!p->jk_java_bridge_class) {
        jk_log(l, JK_LOG_EMERG, "Can't find class %s", btype);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_log(l, JK_LOG_DEBUG,
           "In get_bridge_object, loaded %s bridge class", btype);

    constructor_method_id = (*env)->GetMethodID(env, p->jk_java_bridge_class, strdup_ascii(&p->p, "<init>"),    /* method name */
                                                strdup_ascii(&p->p, "()V"));    /* method sign */

    if (!constructor_method_id) {
        p->jk_java_bridge_class = (jclass) NULL;
        jk_log(l, JK_LOG_EMERG, "Can't find constructor");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p->jk_java_bridge_object = (*env)->NewObject(env,
                                                 p->jk_java_bridge_class,
                                                 constructor_method_id);
    if (!p->jk_java_bridge_object) {
        p->jk_java_bridge_class = (jclass) NULL;
        jk_log(l, JK_LOG_EMERG, "Can't create new bridge object");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    p->jk_java_bridge_object =
        (jobject) (*env)->NewGlobalRef(env, p->jk_java_bridge_object);
    if (!p->jk_java_bridge_object) {
        jk_log(l, JK_LOG_EMERG, "Can't create global ref to bridge object");
        p->jk_java_bridge_class = (jclass) NULL;
        p->jk_java_bridge_object = (jobject) NULL;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

static int get_method_ids(jni_worker_t * p, JNIEnv * env, jk_logger_t *l)
{

/* AS400/BS2000 need conversion from EBCDIC to ASCII before passing to JNI */

    p->jk_startup_method = (*env)->GetMethodID(env,
                                               p->jk_java_bridge_class,
                                               strdup_ascii(&p->p, "startup"),
                                               strdup_ascii(&p->p,
                                                            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"));

    if (!p->jk_startup_method) {
        jk_log(l, JK_LOG_EMERG, "Can't find startup()");
        return JK_FALSE;
    }

    p->jk_service_method = (*env)->GetMethodID(env,
                                               p->jk_java_bridge_class,
                                               strdup_ascii(&p->p, "service"),
                                               strdup_ascii(&p->p, "(JJ)I"));

    if (!p->jk_service_method) {
        jk_log(l, JK_LOG_EMERG, "Can't find service()");
        return JK_FALSE;
    }

    p->jk_shutdown_method = (*env)->GetMethodID(env,
                                                p->jk_java_bridge_class,
                                                strdup_ascii(&p->p,
                                                             "shutdown"),
                                                strdup_ascii(&p->p, "()V"));

    if (!p->jk_shutdown_method) {
        jk_log(l, JK_LOG_EMERG, "Can't find shutdown()");
        return JK_FALSE;
    }

    return JK_TRUE;
}

static JNIEnv *attach_to_jvm(jni_worker_t * p, jk_logger_t *l)
{
    JNIEnv *rc = NULL;
    /* [V] This message is important. If there are signal mask issues,    *
     *     the JVM usually hangs when a new thread tries to attach to it  */
    JK_TRACE_ENTER(l);

#if defined LINUX && defined APACHE2_SIGHACK
    linux_signal_hack();
#endif

    if (0 == (*(p->jvm))->AttachCurrentThread(p->jvm,
#ifdef JNI_VERSION_1_2
                                              (void **)
#endif
                                              &rc, NULL)) {
        jk_log(l, JK_LOG_DEBUG, "In attach_to_jvm, attached ok");
        JK_TRACE_EXIT(l);
        return rc;
    }
    jk_log(l, JK_LOG_ERROR,
           "In attach_to_jvm, cannot attach thread to JVM.");
    JK_TRACE_EXIT(l);
    return NULL;
}

/*
static JNIEnv *attach_to_jvm(jni_worker_t *p)
{
    JNIEnv *rc = NULL;

#ifdef LINUX
    linux_signal_hack();
#endif

    if(0 == (*(p->jvm))->AttachCurrentThread(p->jvm,
#ifdef JNI_VERSION_1_2
           (void **)
#endif
                                             &rc,
                                             NULL)) {
        return rc;
    }

    return NULL;
}
*/
static void detach_from_jvm(jni_worker_t * p, jk_logger_t *l)
{
    JK_TRACE_ENTER(l);
    if (!p->jvm || !(*(p->jvm))) {
        jk_log(l, JK_LOG_ERROR,
               "cannot detach from NULL JVM.");
    }

    if (0 == (*(p->jvm))->DetachCurrentThread(p->jvm)) {
        jk_log(l, JK_LOG_DEBUG, "detached ok");
    }
    else {
        jk_log(l, JK_LOG_ERROR,
               "cannot detach from JVM.");
    }
    JK_TRACE_EXIT(l);
}
#else
int JK_METHOD jni_worker_factory(jk_worker_t **w,
                                 const char *name, jk_logger_t *l)
{
    jk_log(l, JK_LOG_WARNING,
           "Worker '%s' is of type jni, which is deprecated "
           "and will be removed in a future release.",
           name ? name : "(null)");
    if (w)
        *w = NULL;
    return 0;
}
#endif /* JNI_VERSION_1_6 */
