/* 
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2007, Damjan Jovanovic <d a m j a n d o t j o v a t g m a i l d o t c o m>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Damjan Jovanovic <d a m j a n d o t j o v a t g m a i l d o t c o m>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * Damjan Jovanovic <d a m j a n d o t j o v a t g m a i l d o t c o m>
 *
 *
 * mod_java.c -- Java Module
 *
 */

#include <switch.h>
#include <jni.h>


static switch_memory_pool_t *memoryPool = NULL;
static switch_dso_handle_t *javaVMHandle = NULL;

JavaVM *javaVM = NULL;


SWITCH_MODULE_LOAD_FUNCTION(mod_java_load);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_java_shutdown);
SWITCH_MODULE_DEFINITION(mod_java, mod_java_load, mod_java_shutdown, NULL);


static void launch_java(switch_core_session_t *session, const char *data, JNIEnv *env)
{
    jclass Launcher = NULL;
    jmethodID launch = NULL;
    jstring uuid = NULL;
    jstring args = NULL;

    Launcher = (*env)->FindClass(env, "org/freeswitch/Launcher");
    if (Launcher == NULL)
    {
        (*env)->ExceptionDescribe(env);
        goto done;
    }

    launch = (*env)->GetStaticMethodID(env, Launcher, "launch", "(Ljava/lang/String;Ljava/lang/String;)V");
    if (launch == NULL)
    {
        (*env)->ExceptionDescribe(env);
        goto done;
    }

    uuid = (*env)->NewStringUTF(env, switch_core_session_get_uuid(session));
    if (uuid == NULL)
    {
        (*env)->ExceptionDescribe(env);
        goto done;
    }

    args = (*env)->NewStringUTF(env, data);
    if (args == NULL)
    {
        (*env)->ExceptionDescribe(env);
        goto done;
    }

    (*env)->CallStaticVoidMethod(env, Launcher, launch, uuid, args);
    if ((*env)->ExceptionOccurred(env))
        (*env)->ExceptionDescribe(env);

done:
    if (args != NULL)
        (*env)->DeleteLocalRef(env, args);
    if (uuid != NULL)
        (*env)->DeleteLocalRef(env, uuid);
    if (Launcher != NULL)
        (*env)->DeleteLocalRef(env, Launcher);
}

SWITCH_STANDARD_APP(java_function)
{
    JNIEnv *env;
    jint res;

    if (javaVM == NULL)
        return;

    res = (*javaVM)->AttachCurrentThread(javaVM, (void*) &env, NULL);
    if (res == JNI_OK)
    {
        launch_java(session, data, env);
        (*javaVM)->DetachCurrentThread(javaVM);
    }
    else
        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error attaching thread to Java VM!\n");
}


static switch_application_interface_t java_application_interface = {
	/*.interface_name */ "java",
	/*.application_function */ java_function,
	NULL, NULL, NULL,
	/* flags */ SAF_NONE,
	/* should we support no media mode here?  If so, we need to detect the mode, and either disable the media functions or indicate media if/when we need */
	/*.next */ NULL
};

static switch_loadable_module_interface_t java_module_interface = {
	/*.module_name */ modname,
	/*.endpoint_interface */ NULL,
	/*.timer_interface */ NULL,
	/*.dialplan_interface */ NULL,
	/*.codec_interface */ NULL,
	/*.application_interface */ &java_application_interface,
	/*.api_interface */ NULL,
	/*.file_interface */ NULL,
	/*.speech_interface */ NULL,
	/*.directory_interface */ NULL
};

static switch_status_t load_config(JavaVMOption **javaOptions, int *optionCount)
{
    switch_xml_t cfg, xml;
    switch_status_t status;

    xml = switch_xml_open_cfg("java.conf", &cfg, NULL);
    if (xml)
    {
        switch_xml_t javavm;
        switch_xml_t options;

        javavm = switch_xml_child(cfg, "javavm");
        if (javavm != NULL)
        {
            const char *path = switch_xml_attr_soft(javavm, "path");
            if (path != NULL)
            {
                status = switch_dso_load(&javaVMHandle, path, memoryPool);
                if (status != SWITCH_STATUS_SUCCESS)
                    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error loading %s\n", path);
            }
            else
            {
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No Java VM path specified in java.conf.xml\n");
                status = SWITCH_STATUS_FALSE;
            }
        }
        else
        {
            switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No Java VM specified in java.conf.xml\n");
            status = SWITCH_STATUS_FALSE;
            goto close;
        }

        options = switch_xml_child(cfg, "options");
        if (options != NULL)
        {
            switch_xml_t option;
            int i = 0;
            *optionCount = 0;
            for (option = switch_xml_child(options, "option"); option; option = option->next)
            {
                const char *value = switch_xml_attr_soft(option, "value");
                if (value != NULL)
                    ++*optionCount;
            }
            *optionCount += 1;
            *javaOptions = switch_core_alloc(memoryPool, (switch_size_t)(*optionCount * sizeof(JavaVMOption)));
            if (*javaOptions == NULL)
            {
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory!\n");
                status = SWITCH_STATUS_FALSE;
                goto close;
            }
            for (option = switch_xml_child(options, "option"); option; option = option->next)
            {
                const char *value = switch_xml_attr_soft(option, "value");
                if (value == NULL)
                    continue;
                (*javaOptions)[i].optionString = switch_core_strdup(memoryPool, value);
                if ((*javaOptions)[i].optionString == NULL)
                {
                    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Out of memory!\n");
                    status = SWITCH_STATUS_FALSE;
                    goto close;
                }
                ++i;
            }
            (*javaOptions)[i].optionString = "-Djava.library.path=" SWITCH_PREFIX_DIR SWITCH_PATH_SEPARATOR "mod";
        }

    close:
        switch_xml_free(xml);
    }
    else
    {
        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening java.conf.xml\n");
        status = SWITCH_STATUS_FALSE;
    }
    return status;
}

static switch_status_t create_java_vm(JavaVMOption *options, int optionCount)
{
    jint (JNICALL *pJNI_CreateJavaVM)(JavaVM**,void**,void*);
    switch_status_t status;

    status = switch_dso_sym((void*) &pJNI_CreateJavaVM, javaVMHandle, "JNI_CreateJavaVM");
    if (status == SWITCH_STATUS_SUCCESS)
    {
        JNIEnv *env;
        JavaVMInitArgs initArgs;
        jint res;

        memset(&initArgs, 0, sizeof(initArgs));
        initArgs.version = JNI_VERSION_1_4;
        initArgs.nOptions = optionCount;
        initArgs.options = options;
        initArgs.ignoreUnrecognized = JNI_TRUE;

        res = pJNI_CreateJavaVM(&javaVM, (void*) &env, &initArgs);
        if (res == JNI_OK)
        {
            (*javaVM)->DetachCurrentThread(javaVM);
            status = SWITCH_STATUS_SUCCESS;
        }
        else
        {
            switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating Java VM!\n");
            status = SWITCH_STATUS_FALSE;
        }
    }
    else
    {
        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Specified Java VM doesn't have JNI_CreateJavaVM\n");
        status = SWITCH_STATUS_FALSE;
    }
    return status;
}

SWITCH_MODULE_LOAD_FUNCTION(mod_java_load)
{
    switch_status_t status;
    JavaVMOption *options = NULL;
    int optionCount = 0;

    /* connect my internal structure to the blank pointer passed to me */
    *module_interface = &java_module_interface;
    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Java Framework Loading...\n");

    if (javaVM != NULL)
        return SWITCH_STATUS_SUCCESS;

    status = switch_core_new_memory_pool(&memoryPool);
    if (status == SWITCH_STATUS_SUCCESS)
    {
        status = load_config(&options, &optionCount);
        if (status == SWITCH_STATUS_SUCCESS)
        {
            status = create_java_vm(options, optionCount);
            if (status == SWITCH_STATUS_SUCCESS)
                return SWITCH_STATUS_SUCCESS;
            switch_dso_unload(javaVMHandle);
        }
        switch_core_destroy_memory_pool(&memoryPool);
    }
    else
        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating memory pool\n");
    return status;
}

SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_java_shutdown)
{
    if (javaVM == NULL)
        return SWITCH_STATUS_FALSE;

    (*javaVM)->DestroyJavaVM(javaVM);
    javaVM = NULL;
    switch_dso_unload(javaVMHandle);
    switch_core_destroy_memory_pool(&memoryPool);
    return SWITCH_STATUS_SUCCESS;
}


