/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/


//===[ Include files ]=====================================================

#include "internal.h"
#include <jni.h>

//===[ External data ]=====================================================

//===[ External prototypes ]===============================================

//===[ Manifest constants ]================================================

#define MAXFD 64

#define MIN_THREADS 1
#define MAX_THREADS 4096
#define DEFAULT_BEGIN_THREADS 5
#define DEFAULT_GROW_THREADS 5

#define DOMAIN_SOCKET_FILE_NAME "/var/lib/CASA/authtoken/validate/socket"

//===[ Type definitions ]==================================================

//===[ Function prototypes ]===============================================

void*
WorkerThread(void*);

//===[ Global variables ]==================================================

// Usage string
char  usage[] = "\nCasaAuthtokenValidateD: usage: [-p ListenPort] [-b BeginThreads] [-g GrowThreads] [-m MaxThreads] [-D DebugLevel] [-d] [-s]\n";

// Worker thread pool configuration parameters
int   beginThreads = DEFAULT_BEGIN_THREADS;
int   growThreads = DEFAULT_GROW_THREADS;
int   maxThreads = MAX_THREADS;
int   minWaitingThreads = beginThreads;
int   maxWaitingThreads = beginThreads * 4;

// Worker thread pool operating parameters
double   numThreads = 0;
double   numBusyThreads = 0;
double   numPerishingThreads = 0;

// Listen Port Number
//int   listenPortNumber = 5000;
unsigned short int   listenPortNumber = 0;

// Parameter indicating whether or not the server needs to run
// as a daemon.
bool  daemonize = false;

// Name to use for logging purposes
char  appName[] = "CasaAuthtokenValidateD";

// Debug Level
int   DebugLevel = 0;
bool  UseSyslog = false;

// Variables for daemon auto-restart after crash feature
static bool autoRestartAfterCrash = true;

// Synchronization variables
pthread_mutex_t   interlockedMutex;
pthread_mutex_t   serverMutex;
pthread_cond_t    serverCondition;

// Operating parameters
bool  singleThreaded = false;
bool  terminating = false;

// Java parameters
JavaVM   *g_jvm = NULL;
JNIEnv   *g_env = NULL;

char  classpath[] = "-Djava.class.path=/usr/share/java/CASA/authtoken/CasaJaasSupport.jar:/usr/share/java/CASA/authtoken/CasaAuthToken.jar:/usr/share/java/log4j.jar:/usr/share/java/commons-logging.jar:/usr/share/java/CASA/authtoken/external/apache.org/xmlsec-1.4.0.jar:/usr/share/java/xerces-j2.jar:/etc/CASA/authtoken/keys/client";
// Java AuthenticationToken Class and method name
//char  authTokenClassName[] = "jtest";
//char  authTokenClassValidateMethodName[] = "test4";
char  authTokenClassName[] = "com/novell/casa/authtoksvc/AuthToken";
char  authTokenClassValidateMethodName[] = "validate";


//++=======================================================================
void
ServiceRequests(void)
//
//  Arguments: 
//
//  Returns:
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "ServiceRequests- Start\n", 0);

   // We are now attached to the JVM, find the helper class that
   // we need.
   jclass helperClass = g_env->FindClass(authTokenClassName);
   if (helperClass)
   {
      // Helper class found, now get the id of the method that we invoke
      jmethodID mId = g_env->GetStaticMethodID(helperClass,
                                               authTokenClassValidateMethodName,
                                               "(Ljava/lang/String;)Ljava/lang/String;");
      if (mId)
      {
         // Loop until told to terminate
         while (!terminating)
         {
            // Get a request that needs servicing
            uint32_t requestId = IpcServerGetRequest();
            if (requestId != 0)
            {
               // We got a request that needs servicing, now get the
               // data associated with it.
               char *pReqData;
               int dataLen = IpcServerGetRequestData(requestId, &pReqData);
               if (dataLen != 0)
               {
                  // Lets push the jvm local frame to allow us to clean up our local
                  // references later.
                  g_env->PushLocalFrame(10);

                  // Encapsulate the request data into a string object
                  jstring inString = g_env->NewStringUTF(pReqData);
                  if (inString)
                  {
                     // Invoke our helper method
                     jstring outString = (jstring) g_env->CallStaticObjectMethod(helperClass, mId, inString);

                     // Check if an excption occurred
                     if (g_env->ExceptionCheck() == JNI_TRUE)
                     {
                        // There is a pending exception, display the info which in turn clears it.
                        g_env->ExceptionDescribe();
                        IpcServerAbortRequest(requestId);
                     }
                     else
                     {
                        if (outString)
                        {
                           // The helper method succeded, complete the request
                           // with the data returned.
                           const char *pOutChars = g_env->GetStringUTFChars(outString, NULL);
                           if (pOutChars)
                           {
                              IpcServerCompleteRequest(requestId, (char*) pOutChars);
                              g_env->ReleaseStringUTFChars(outString, pOutChars);
                           }
                           else
                           {
                              DbgTrace(0, "ServiceRequests- Unable to get UTF characters\n", 0);
                              IpcServerAbortRequest(requestId);
                           }
                        }
                        else
                        {
                           // The helper method failed, just abort the request.
                           IpcServerAbortRequest(requestId);
                        }
                     }
                  }
                  else
                  {
                     DbgTrace(0, "ServiceRequests- UTF String allocation failure\n", 0);
                     IpcServerAbortRequest(requestId);
                  }

                  // Pop the jvm local frame to clean up our local references
                  g_env->PopLocalFrame(NULL);
               }
               else
               {
                  DbgTrace(0, "ServiceRequests- Error obtaining Request data\n", 0);
                  IpcServerAbortRequest(requestId);
               }
            }
            else
            {
               // No need to service requests any longer
               break;
            }
         }
      }
      else
      {
         DbgTrace(0, "ServiceRequests- Failed to get method id\n", 0);
      }
   }
   else
   {
      DbgTrace(0, "ServiceRequests- Failed to find helper class\n", 0);
   }

   DbgTrace(1, "ServiceRequests- End\n", 0);

}  /*-- ServiceRequests() --*/


//++=======================================================================
void
GrowWorkerThreadPool(int growNumber)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes: The serverMutex needs to be held when calling this
//         procedure.
//
// L2
//=======================================================================--
{
   DbgTrace(1, "GrowWorkerThreadPool- Start\n", 0);

   for (int i = 0; i < growNumber; i++)
   {
      int threadCreateStatus;
      pthread_t thread;

      if ((threadCreateStatus = pthread_create(&thread,
                                               NULL,
                                               (void*(*)(void*))WorkerThread,
                                               NULL) == 0))
      {
         // Worker thread created
         numThreads ++;
      }
      else
      {
         DbgTrace(0, "GrowWorkerThreadPool- Thread creation failed, status = %0d\n", threadCreateStatus);
      }
   }

   // Let our server know if we ended up with no worker threads
   if (numThreads == 0)
      pthread_cond_signal(&serverCondition);

   DbgTrace(1, "GrowWorkerThreadPool- End\n", 0);

}  /*-- GrowWorkerThreadPool() --*/


//++=======================================================================
void
WorkerThreadBusy(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "WorkerThreadBusy- Start\n", 0);

   // Acquire our mutex
   pthread_mutex_lock(&serverMutex);

   // Increment the numBusyThread count and grow the number of worker threads
   // if necessary.
   numBusyThreads ++;
   if ((numThreads - numBusyThreads) < minWaitingThreads)
      GrowWorkerThreadPool(growThreads);

   // Release our mutex
   pthread_mutex_unlock(&serverMutex);

   DbgTrace(1, "WorkerThreadBusy- End\n", 0);

}  /*-- WorkerThreadBusy() --*/


//++=======================================================================
bool
WorkerThreadWaiting(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   bool  retValue;

   DbgTrace(1, "WorkerThreadWaiting- Start\n", 0);

   // Acquire our mutex
   pthread_mutex_lock(&serverMutex);

   // Decrement the numBusyThread count
   numBusyThreads --;

   // Check if we have too many idle workers
   if ((numThreads - numBusyThreads - numPerishingThreads) > maxWaitingThreads
       && numThreads > beginThreads)
   {
      // We want to let this worker perish
      numPerishingThreads ++;
      retValue = true;
   }
   else
      retValue = false;

   // Release our mutex
   pthread_mutex_unlock(&serverMutex);

   DbgTrace(1, "WorkerThreadWaiting- End, retValue = %X\n", retValue);

   return retValue;

}  /*-- WorkerThreadWaiting() --*/


//++=======================================================================
void*
WorkerThread(void*)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   bool  perishingThread = false;

   DbgTrace(1, "WorkerThread- Start\n", 0);

   // Set the thread in the detached state so that it is cleaned up when it exits
	pthread_detach(pthread_self());

   // Attach the thread to the JVM
   JNIEnv *env;
   JavaVMAttachArgs attachArgs = {0};
   attachArgs.version = JNI_VERSION_1_4;
   if (g_jvm->AttachCurrentThread((void**) &env, &attachArgs) >= 0)
   {
      // We are now attached to the JVM, find the helper class that
      // we need.
      jclass helperClass = env->FindClass(authTokenClassName);
      if (helperClass)
      {
         // Helper class found, now get the id of the method that we invoke
         jmethodID mId = env->GetStaticMethodID(helperClass,
                                                authTokenClassValidateMethodName,
                                                "(Ljava/lang/String;)Ljava/lang/String;");
         if (mId)
         {
            // Loop until told to terminate
            while (!terminating)
            {
               // Get a request that needs servicing
               int32_t requestId = IpcServerGetRequest();
               if (requestId != 0)
               {
                  // We got a request that needs servicing, now get the
                  // data associated with it.
                  char *pReqData;
                  int dataLen = IpcServerGetRequestData(requestId, &pReqData);
                  if (dataLen != 0)
                  {
                     // Indicate that we are now busy
                     WorkerThreadBusy();

                     // Lets push the jvm local frame to allow us to clean up our local
                     // references later.
                     env->PushLocalFrame(10);

                     // Encapsulate the request data into a string object
                     jstring inString = env->NewStringUTF(pReqData);
                     if (inString)
                     {
                        // Invoke our helper method
                        jstring outString = (jstring) env->CallStaticObjectMethod(helperClass, mId, inString);

                        // Check if an excption occurred
                        if (env->ExceptionCheck() == JNI_TRUE)
                        {
                           // There is a pending exception, display the info which in turn clears it.
                           env->ExceptionDescribe();
                           IpcServerAbortRequest(requestId);
                        }
                        else
                        {
                           if (outString)
                           {
                              // The helper method succeded, complete the request
                              // with the data returned.
                              const char *pOutChars = env->GetStringUTFChars(outString, NULL);
                              if (pOutChars)
                              {
                                 IpcServerCompleteRequest(requestId, (char*) pOutChars);
                                 env->ReleaseStringUTFChars(outString, pOutChars);
                              }
                              else
                              {
                                 DbgTrace(0, "WorkerThread- Unable to get UTF characters\n", 0);
                                 IpcServerAbortRequest(requestId);
                              }
                           }
                           else
                           {
                              // The helper method failed, just abort the request.
                              IpcServerAbortRequest(requestId);
                           }
                        }
                     }
                     else
                     {
                        DbgTrace(0, "WorkerThread- UTF String allocation failure\n", 0);
                        IpcServerAbortRequest(requestId);
                     }

                     // Pop the jvm local frame to clean up our local references
                     env->PopLocalFrame(NULL);

                     // Indicate that we are no longer busy and get indication of
                     // whether or not we should continue to try to process requests.
                     if (WorkerThreadWaiting() == true)
                     {
                        DbgTrace(1, "WorkerThread- Requested to terminate\n", 0);

                        // Remember that we are a perishing thread so that we can reduce the
                        // count as we exit.
                        perishingThread = true;
                        break;
                     }
                  }
                  else
                  {
                     DbgTrace(0, "WorkerThread- Error obtaining Request data\n", 0);
                     IpcServerAbortRequest(requestId);
                  }
               }
               else
               {
                  // No need to service requests any longer
                  break;
               }
            }
         }
         else
         {
            DbgTrace(0, "WorkerThread- Failed to get method id\n", 0);
         }
      }
      else
      {
         DbgTrace(0, "WorkerThread- Failed to find helper class\n", 0);
      }

      // Detach from the JVM
      g_jvm->DetachCurrentThread();
   }
   else
   {
      DbgTrace(0, "WorkerThread- Failed to attach to JVM\n", 0);
   }

   // Decrement the number of worker threads and signal our main thread
   // to terminate itself if we are the last worker thread.
   pthread_mutex_lock(&serverMutex);

   if (perishingThread)
      numPerishingThreads --;

   numThreads --;
   if (numThreads == 0)
      pthread_cond_signal(&serverCondition);

   pthread_mutex_unlock(&serverMutex);

   DbgTrace(1, "WorkerThread- End\n", 0);

   // Exit
	pthread_exit(NULL);

	return 0;	// never-reached!

}  /*-- WorkerThread() --*/


//++=======================================================================
void
SigTermHandler(
   int signum)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "SigTermHandler- Start\n", 0);

   // Indicate that we are terminating
   terminating = true;

   // Shutdown the IPC Server
   IpcServerShutdown();

   DbgTrace(1, "SigTermHandler- End\n", 0);

}  /*-- SigTermHandler() --*/


//++=======================================================================
int
InitJavaInvoke(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   int   retStatus = -1;

   DbgTrace(1, "InitJavaInvoke- Start\n", 0);

   JavaVMOption options[6];
   options[0].optionString = classpath;
   options[1].optionString = "-Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser";
   options[2].optionString = "-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl";
   options[3].optionString = "-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl";
   //options[4].optionString = "-Xcheck:jni";
   //options[5].optionString = "-Djaxp.debug=1";
   JavaVMInitArgs vm_args;
   vm_args.version = JNI_VERSION_1_4;
   vm_args.options = options;
   vm_args.nOptions = 4;
   vm_args.ignoreUnrecognized = true;
   if (JNI_CreateJavaVM(&g_jvm, (void**)&g_env, &vm_args) >= 0)
   {
      // Success
      retStatus = 0;
   }
   else
   {
      DbgTrace(0, "InitJavaInvoke- Error creating Java VM\n", 0);
   }

   DbgTrace(1, "InitJavaInvoke- End, retStatus = %0X\n", retStatus);

   return retStatus;

}  /*-- InitJavaInvoke() --*/


//++=======================================================================
void
UnInitJavaInvoke(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "UnInitJavaInvoke- Start\n", 0);

   // Destroy the jvm
   if (g_jvm)
   {
      g_jvm->DestroyJavaVM();
      g_jvm = NULL;
   }
   g_env = NULL;

   DbgTrace(1, "UnInitJavaInvoke- End\n", 0);

}  /*-- UnInitJavaInvoke() --*/


//++=======================================================================
void
DaemonInit(
   const char *pname)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes: Copy of daemon_init() in Richard Stevens Unix Network
//         Programming Book.
//
// L2
//=======================================================================--
{
   pid_t             pid;
   char              *pNoAutoRestartEnvvar;

   DbgTrace(1, "DaemonInit- Start\n", 0);

   // Determine if we need to disable the auto-restart after crash feature
   if ((pNoAutoRestartEnvvar = getenv("CASA_NO_AUTORESTART_AFTER_CRASH")) != NULL
       && strcmp(pNoAutoRestartEnvvar, "0") != 0)
   {
      DbgTrace(1, "DaemonInit- Disabling daemon auto-restart after crash feature\n", 0);
      autoRestartAfterCrash = false;
   }

   // Fork to run in the background, check for error.
   if ((pid = fork()) == -1)
   {
      DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
      exit(0);
   }
   else if (pid != 0)
   {
      // The fork succeeded and we are the parent process, terminate
      // ourselves.
      exit(0);
   }

   /* 1st child continues */

   // Become the session leader and set to ignore SIGHUP
   setsid();
   signal(SIGHUP, SIG_IGN);

   // Fork again to guarantee that the daemon can not acquire a
   // controlling terminal.
   if ((pid = fork()) == -1)
   {
      DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
      exit(0);
   }
   else if (pid != 0)
   {
      // The fork succeeded and we are the parent process, terminate
      // ourselves.
      exit(0);
   }

   /* 2nd child continues */

   // Close any open descriptors
   for (int i = 0; i < MAXFD; i++)
      close(i);

   // Spawn a worker
   if ((pid = fork()) == -1)
   {
      DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
      exit(0);
   }
   else if (pid == 0)
   {
      // The fork succeeded and we are the worker, continue.
   }
   else
   {
      // We are the parent of the server, check if we must execute the auto-restart
      // server after crash logic.
      if (autoRestartAfterCrash)
      {
         // Execute auto-restart server after crash logic
         while (1)
         {
            int childExitStatus;

            // Wait for children to exit
            pid = wait(&childExitStatus);
            if (pid != -1)
            {
               // Fork worker
               if ((pid = fork()) == -1)
               {
                  DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
                  exit(0);
               }
               else if (pid == 0)
               {
                  // The fork succeeded and we are the server, exit the loop
                  // to start.
                  goto childContinue;
               }

               // We are the parent process, continue to watch for a terminated child process.
               syslog(LOG_USER | LOG_INFO, "CasaAuthtokenValidateD: Worker re-started after it terminated unexpectedly");
               sleep(1);   // To keep from consuming too many cycles
            }
            else
            {
               // Check if we must exit the loop
               if (errno != EINTR)
                  break;
            }
         }
      }
      
      // Terminate ourselves.
      exit(0);
   }

childContinue:

   // Set flag to inform DbgTrace macros to use Syslog
   UseSyslog = true;

   // Change the working directory
   chdir("/var/lib/CASA/authtoken/validate");

   // Clear our file mode creation mask
   umask(0);

   // Get ready to log
   openlog(appName, LOG_CONS | LOG_NOWAIT | LOG_ODELAY| LOG_PID, LOG_USER);

   if (DebugLevel == 0)
      setlogmask(LOG_UPTO(LOG_INFO));
   else
      setlogmask(LOG_UPTO(LOG_DEBUG));

   DbgTrace(1, "DaemonInit- End\n", 0);

}  /*-- DaemonInit() --*/


//++=======================================================================
int
main(
   int argc,
   char* argv[])
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   int         optionsSpecified = 0;
   bool        doneScanning = false;
   bool        invalidOption = false;
   int         option;

   //printf("**** AuthTokenValidate Daemon ****\n");

   // Scan through the options specified
   while (!doneScanning)
   {
      long int value = 0;
      opterr = 0;
      option = getopt(argc, argv, "m:p:b:g:D:ds");

      // Proceed based on the result
      switch (option)
      {
         case 'p':
            // Port number option, record location of
            // argument.
            errno = 0;
            value = strtol(optarg, (char**) NULL, 10);
            if (errno == 0
                && value > 0
                && value <= USHRT_MAX)
            {
               listenPortNumber = (unsigned short int) value;
            }
            else
            {
               fprintf(stderr, "Specified ListenPort parameter out of range, using default value");
            }
            optionsSpecified ++;
            break;

         case 'b':
            // Begin threads option, override the default parameter
            // with the value of the option.
            errno = 0;
            value = strtol(optarg, (char**) NULL, 10);
            if (errno == 0
                && value >= MIN_THREADS
                && value <= MAX_THREADS)
            {
               beginThreads = (int) value;
            }
            else
            {
               fprintf(stderr, "Specified BeginThreads parameter out of range, using default value");
            }

            optionsSpecified ++;
            break;

         case 'g':
            // Grow threads option, override the default parameter
            // with the value of the option.
            errno = 0;
            value = strtol(optarg, (char**) NULL, 10);
            if (errno == 0
                && value >= MIN_THREADS
                && value <= MAX_THREADS)
            {
               growThreads = (int) value;
            }
            else
            {
               fprintf(stderr, "Specified GrowThreads parameter out of range, using default value");
            }

            optionsSpecified ++;
            break;

         case 'm':
            // Max threads option, override the default parameter
            // with the value of the option.
            errno = 0;
            value = strtol(optarg, (char**) NULL, 10);
            if (errno == 0
                && value >= MIN_THREADS
                && value <= MAX_THREADS)
            {
               maxThreads = (int) value;
            }
            else
            {
               fprintf(stderr, "Specified MaxThreads parameter out of range, using default value");
            }

            optionsSpecified ++;
            break;

         case 'd':
            // Run as daemon option
            daemonize = true;
   
            optionsSpecified ++;
            break;

         case 's':
            // Run single threaded
            singleThreaded = true;
   
            optionsSpecified ++;
            break;

         case 'D':
            // Set the debug level
            DebugLevel = atoi(optarg);
            optionsSpecified++;
            break;

         case '?':
            // Invalid option detected
            doneScanning = true;
            invalidOption = true;
            break;

         default:
            // Done scanning
            doneScanning = true;
            break;
      }
   }

   // Do some sanity checking
   if (!invalidOption
       && beginThreads > 0
       && maxThreads > (growThreads+beginThreads)
       && beginThreads <= maxThreads)
   {
      // The server is ready to start, check if we must
      // run it as a daemon.
      if (daemonize)
         DaemonInit(argv[0]);

      // Set a handler for SIGTERM
      signal(SIGTERM, SigTermHandler);

      // Initialize our mutexes
      pthread_mutex_init(&interlockedMutex, NULL);
      pthread_mutex_init(&serverMutex, NULL);

      // Initialize the JVM
      if (InitJavaInvoke() == 0)
      {
         // Initialize the condition that we will use to wait
         // for the exit of all of our worker threads.
         if (pthread_cond_init(&serverCondition, NULL) == 0)
         {
            // Initialize the IPC Server
            if (IpcServerInit(appName,
                              DebugLevel,
                              UseSyslog) == 0)
            {
               // Now setup the appropriate listen address
               int setAddressResult;
               if (listenPortNumber == 0)
                  setAddressResult = IpcServerSetUnAddress(DOMAIN_SOCKET_FILE_NAME);
               else
                  setAddressResult = IpcServerSetInAddress(listenPortNumber);

               if (setAddressResult == 0)
               {
                  // Now start the IPC server
                  if (IpcServerStart() == 0)
                  {
                     // Proceed according to run model configured
                     if (singleThreaded)
                     {
                        // Runninf in single-threaded model
                        ServiceRequests();
                     }
                     else
                     {
                        // Running in multi-threaded model
                        // 
                        // Acquire our mutex
                        pthread_mutex_lock(&serverMutex);

                        // Start worker threads
                        GrowWorkerThreadPool(beginThreads);

                        // Wait for the worker threads to terminate
                        pthread_cond_wait(&serverCondition, &serverMutex);

                        // Release our mutex
                        pthread_mutex_unlock(&serverMutex);
                     }

                     DbgTrace(0, "main- Exiting, numThreads = %d\n", numThreads);
                  }
               }
               else
               {
                  DbgTrace(0, "main- Setting of listen address failed\n", 0);
               }
            }
            else
            {
               DbgTrace(0, "main- Initialization of Ipc server failed\n", 0);
            }
         }
         else
         {
            DbgTrace(0, "main- Condition initialization failed\n", 0);
         }
         // Un-initialize JVM
         UnInitJavaInvoke();
      }
      else
      {
         DbgTrace(0, "main- JVM initialization failed\n", 0);
      }
   }
   else
   {
      // Invalid option detected or the user failed to
      // specify the listening port number.
      fprintf(stderr, usage, argv[0]);
   }

   return 0;

}  /*-- main() --*/


//=========================================================================
//=========================================================================