ServerDriver.java

/*
** Module   : ServerDriver.java
** Abstract : command line driver for the server
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 NVS 20050418   @20786 Repackaging net package. This file has been created as a
**                           generalized P2J server driver.
** 002 NVS 20050427   @20915 Introduced a way of passing application's arguments and options
**                           to the application and get its feedback on init() and arguments()
**                           calls. "false" returned from either will halt application execution.
** 003 NVS 20050830   @22423 Changed to bring the server to a production level.
** 004 GES 20060815   @28612 Refactored to remove duplicate code and share a common base class.
**                           Added kill and status modes.
** 005 GES 20060816   @28616 Cleaned up new modes/docs and added a wait mode.
** 006 GES 20061009   @30217 Minor fix to arguments processing.
** 007 GES 20061215   @31706 Changes in bootstrap config interface.
** 008 GES 20070111   @31781 Hard coded server class as StandardServer and removed some
**                           unnecessary code.
** 009 GES 20070112   @31813 Removed initial debug level (it wasn't being used downstream) and
**                           added application args.
** 010 EVL 20070609   @34005 Adding explicit import of the class com.goldencode.p2j.net.Queue
**                           to eliminate conflict with the same class from java.util package
**                           to be able to compile for Java 6.
** 011 ECF 20071101   @35891 Refactored to accommodate net package mods. Eliminated
**                           ServerBootstrap dependencies, integrated SessionManager.
** 012 GES 20080118   @36864 Output any abend info to STDERR not STDOUT. For servers, STDERR is
**                           the main log file and for clients STDOUT is interactive and can't
**                           be the destination. If STDERR on the client is redirected to file,
**                           then a record of any abend will be available.
** 013 GES 20110921          Minor javadoc fix.
** 014 GES 20111028          Added support for startup of a client session within the same
**                           process as the server.
** 015 GES 20111110          Single process mode now gets a unique initial context so client
**                           stuff is not shared to server threads. In addition, this makes it
**                           possible for multiple client sessions to co-exist in the same server
**                           session (with different initial contexts).
** 016 CA  20130705          Added appserver launching from server argument.
** 017 CA  20130822          Fixed the net protocol to support async requests.
** 018 CA  20140207          Added argument to reconfigure the private keys and certificates.
**                           Changed appserver launcher argument from "-p" to "-j".
** 019 CA  20140225          Fixed remote server connections when the key store is saved in the 
**                           directory: we need to act as a server to initialize the transport 
**                           security, then go back as a client when connecting to the remote 
**                           server.
** 020 GES 20141228          Modified start() signature to match changes elsewhere.
** 021 EVL 20160225          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 022 GES 20170421          Removed references to application arguments (which is a feature that
**                           was removed from CommonDriver in H008. Changed default bootstrap
**                           name to server.xml.
** 023 ECF 20190451          Added -m{1|0} options for method execution tracing.
** 024 IAS 20200930          Added JMX instrumentation.
**     IAS 20210827          Added BouncyCastle JCE/JSSE support
**     GES 20210827          Added driver name initialization and diagnostics output.
**     OM  20210923          Added -profile command line option for specifying the configuration profile.
**     IAS 20220624          Added support for the Conscrypt JCE/JSSE provider
**     CA  20230130          Added options to trim or restart the agents in an appserver.
** 025 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 026 GBB 20230517          Mark BootstrapConfig in configureLogging as server.
** 027 GBB 20230519          Resolve logger mode after server params are parsed and server mode is determined.
** 028 GBB 20230530          configureLogging to fallback to default configs on ConfigurationException.
** 029 GBB 20230825          SecurityManager context methods calls updated.
**                           A default exception handler registered.
** 030 HC  20240222          Enabled JMX on FWD Client.
** 031 GBB 20240709          Hard-coded config names replaced by ConfigItem constants.
** 032 GBB 20240729          Initialize CentralLogger after SecurityManager is created.
** 033 GBB 20240826          Removing SingleProcessClient.
** 034 VVT 20250303          A warning is logged if the default character set differs from the only supported
**                           UTF-8. See #7944.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.main;

import java.net.*;
import java.nio.charset.*;

import com.goldencode.p2j.aspects.ltw.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.jmx.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;

/**
 * General purpose command line driver to start a server process based on a
 * bootstrap configuration file and/or command line parameters.
 * <p>
 * Configuration file can be plain (unencrypted) XML file or encrypted XML.
 * If no password is given with the command line, the file is assumed to be
 * plain XML.
 * <p>
 * This driver takes an optional name of a bootstrap configuration file, but
 * if not specified, it will default to "standard_server.xml" in the current
 * directory.
 * <p>
 * All entries in the bootstrap configuration can be overridden by a command
 * line parameter using the "category:group:key=value" syntax (see below).
 * <p>
 * The bootstrap configuration can specify a custom class as the server
 * process by using the "applications:class:server" key.  If not specified
 * in the configuration file or on the command line, its value will be 
 * defaulted to {@link StandardServer}.
 * <p>
 * The full syntax of the command line is:
 * <p>
 * <code>
 * java ServerDriver [-?]
 *                   [-k]
 *                   [-s]
 *                   [-c]
 *                   [-w timeout]
 *                   [-b batch-process]
 *                   [-j appserver [restart|trim]]
 *                   [<i>config</i>]
 *                   [-p <i>config_file_password</i>]
 *                   [-m{1|0}]
 *                   [[[category:]group:]key=value]...  
 * </code>
 * <p>
 * Arguments can be specified in any order, except for the -? (help) which can only be
 * specified as the first argument.
 * <p>
 * '-?' requests syntax help and exits.
 * <p>
 * '-b' will connect to the specified P2J server and attempt to start the specified batch process.
 * <p>
 * '-j' will connect to the specified P2J server and attempt to start the specified appserver.  
 * When <code>trim</code> is specified, then the appserver agents will be trimmed.
 * When <code>restart</code> is specified, then the appserver agents will be restarted.
 * <p>
 * '-c' will reconfigure the certificates, keystores and truststores and save them in the 
 * directory.
 * <p>
 * '-k', '-s' and '-w' options all operate on a the specified server (which
 * is referenced in the bootstrap configuration and/or the command line
 * configuration overrides). These shift into a special "client" mode to
 * connect (or attempt to connect) to the specified server.  The '-k' will
 * kill that server as long as the process' identity has "shutdown" rights
 * on the P2J server.  The '-s' queries the status of the server and the
 * '-w' waits for the server to be in a <code>STATUS_RUNNING</code> state. 
 * The timeout for the '-w' option is in milliseconds and -1 means to wait
 * indefinitely.  All 3 of these special modes of the driver will always 
 * call <code>System.exit</code> with a process exit code that is one of the 
 * following (see the named class constants for the values and meanings):
 * <p>
 * <pre>
 * STATUS_RUNNING
 * STATUS_TIMEOUT
 * STATUS_UNKNOWN
 * STATUS_STOPPED
 * </pre>
 * <p>
 * The exit code in each mode is the current state of the server at the 
 * <b>end</b> of the requested operation.
 * <p>
 * Password can be given with the command line or entered interactively. 
 * One special value can be used: "?" means interactive password input.
 * <p>
 * The '-m1' option starts method tracing data collection; 'm0' is used to stop it and to process
 * the collected data. Method execution tracing introduces significant overhead to the operation
 * of the server and therefore is intended to be a development-time feature only. It is enabled
 * by adding {@code -javaagent:<path_to_jars>/aspectjweaver[version].jar} to the server's JVM
 * command line options. Like the '-k', '-s', and '-w' options, the '-m1' and '-m0' options
 * operate on the separate server process referenced in the bootstrap configuration and/or the
 * command line configuration overrides.
 * <p>
 * Arguments in the form "category:group:key=value" are bootstrap 
 * configuration overrides. They may be used to add or change values of the
 * configuration keys in the specified category and group. A short form of
 * override (one that omits the category and group) is allowed after a long
 * form override and refers to the most recently specified category and 
 * group.
 */
public class ServerDriver
extends CommonDriver
{
   /** Status flag indicates a working/running server. */
   public static final int STATUS_RUNNING = 0;
   
   /** 
    * Status flag indicates a server which is accepting connections but not 
    * responding to transaction requests.
    */
   public static final int STATUS_TIMEOUT = 1;
   
   /** 
    * Status flag indicates a server which is accepting connections but some 
    * failure occurred during the transaction request that was used to check
    * the server's status.
    */
   public static final int STATUS_UNKNOWN = 2;
   
   /** Status flag indicates a server that is not currently running. */
   public static final int STATUS_STOPPED = 3;
   
   /** Mode flag to start the server (the default). */
   private static final int MODE_START = 0;
   
   /** Mode flag to stop the server. */
   private static final int MODE_STOP = 1;
   
   /** Mode flag to check if the server is running. */
   private static final int MODE_STATUS = 2;
   
   /** Mode flag to wait for the server to be running. */
   private static final int MODE_WAIT = 3;
   
   /** Mode flag to start an appserver. */
   private static final int MODE_START_APPSERVER = 4;
   
   /** Mode flag to reconfigure the server's security data. */
   private static final int MODE_RECONFIGURE = 5;
   
   /** Mode flag to start a batch process. */
   private static final int MODE_START_BATCH_PROCESS = 6;
   
   /** Mode flag to start collecting method trace data. */
   private static final int MODE_START_METHOD_TRACE = 7;
   
   /** Mode flag to stop collecting method trace data. */
   private static final int MODE_STOP_METHOD_TRACE = 8;

   /** Logger. Should be instantiated after setting the logger to server mode. */
   private static CentralLogger LOG;
   
   /** Mode flag to restart the agents in an appserver. */
   private static final int MODE_RESTART_APPSERVER = 9;
   
   /** Mode flag to trim the agents in an appserver. */
   private static final int MODE_TRIM_APPSERVER = 10;
   
   /** Flag that allows overriding the default application. */
   private int mode = MODE_START;
   
   /** Maximum milliseconds to wait in <code>MODE_WAIT</code> or -1 for an infinite wait. */
   private int timeout = 0;
   
   /** The name of the remote appserver to start. */
   private String appServer;
   
   /** The name of the remote batch process to start. */
   private String batchProcess;
   
   /** The optional configuration profile. */
   private String cfgProfile = null;
   
   {
      // initialize our protected members
      
      driverName = "ServerDriver";
      defCfg = "server.xml";
      
      helptxt = new String[]
      {
         "------------------------------- P2J Server Driver -------------------------------",
         "",
         "Syntax:",
         "     java ServerDriver -?",
         "                       [-c]",
         "                       [-k]",
         "                       [-s]",
         "                       [-w timeout]",
         "                       [-b batch-process]",
         "                       [-j <appserver> [trim|restart]]",
         "                       [-profile profile-name]",
         "                       [config]",
         "                       [-p config_file_password]",
         "                       [parameter_overrides]",
         "",
         "Where:",
         "",
         "-?                    = displays this help screen",
         "-c                    = reconfigure the directory's truststore, certificates and",
         "                        keystores for the defined servers and processes",
         "-k                    = kill the server specified by this config",
         "-s                    = check the status of the server specified by this config",
         "-w                    = wait for the status of the server specified by this",
         "                        config to be STATUS_RUNNING or for the given timeout,",
         "                        whichever comes first",
         "-b <batch-process>    = start the specified batch process, in the context of the",
         "                        remote P2J server",
         "-profile profile-name = The optional configuration profile name",
         "-j <appserver>        = start the specified appserver, in the context of the",
         "                        remote P2J server.  Optionally, 'trim' or 'restart' commands can be",
         "                        set to either trim or restart the agents in the specified appserver",
         "timeout               = milliseconds to wait with -1 meaning an indefinite wait",
         "config                = a relative or absolute filename of a valid bootstrap",
         "                        configuration file for this server",
         "config_file_password  = the password to use to decrypt the config file or '?' to",
         "                        force an interactive password prompt",
         "parameter_overrides   = configuration values that add to or replace values that",
         "                        can be specified in the bootstrap configuration;",
         "                        the override must take one of three forms:",
         "                           category:group:key=value",
         "                           group:key=value",
         "                           key=value",
         "                        (the shorter forms will use the most recently specified",
         "                        category and/or group as needed)",
         "",
         "All arguments can be specified in any order except '-?' which must always", 
         "appear first.",
         "",
         "Exit codes for '-k', '-s' and '-w' options:",
         "   " + STATUS_RUNNING + "  = STATUS_RUNNING",
         "   " + STATUS_TIMEOUT + "  = STATUS_TIMEOUT",
         "   " + STATUS_UNKNOWN + "  = STATUS_UNKNOWN",
         "   " + STATUS_STOPPED + "  = STATUS_STOPPED",
         "Normally success would mean that '-k' would return a",
         "STATUS_STOPPED and a '-s' or '-w' would return a STATUS_RUNNING." 
      };
   }

   /**
    * Process arguments for any data or options that are specific to the
    * subclass.  This method is called in a loop from
    * {@link CommonDriver#process} and should call the superclass version if
    * the current parameter is not processed.
    *
    * @param    args
    *           The command line arguments.
    * @param    current
    *           The current element being processed.
    *
    * @return   The number of array elements that have been processed by
    *           this method and which MUST be skipped (no processing in
    *           the base class).
    */
   protected int processArguments(String[] args, int current)
   {
      int skip = 0;
      
      // kill option
      if (args[current].equals("-k"))
      {
         mode = MODE_STOP;
         skip = 1;
      }

      // reconfiguration option
      if (args[current].equals("-c"))
      {
         mode = MODE_RECONFIGURE;
         skip = 1;
      }

      // status check option
      if (args[current].equals("-s"))
      {
         mode = MODE_STATUS;
         skip = 1;
      }

      // start method trace option
      if (args[current].equals("-m1"))
      {
         mode = MODE_START_METHOD_TRACE;
         skip = 1;
      }
      
      // stop method trace option
      if (args[current].equals("-m0"))
      {
         mode = MODE_STOP_METHOD_TRACE;
         skip = 1;
      }
      
      // wait option
      if (args[current].equals("-w"))
      {
         testFollowing(args, current, 1, true, "timeout");
         timeout = Integer.parseInt(args[current + 1]);
         mode = MODE_WAIT;
         skip = 2;
      }
      
      // batch process startup
      if (args[current].equals("-b"))
      {
         mode = MODE_START_BATCH_PROCESS;
         batchProcess = args[current + 1];
         skip = 2;
      }
      
      // appserver startup
      if (args[current].equals("-j"))
      {
         mode = MODE_START_APPSERVER;
         appServer = args[current + 1];
         skip = 2;
         
         if (current + 2 <= args.length - 1)
         {
            String nxt = args[current + 2];
            if (nxt.equals("restart"))
            {
               mode = MODE_RESTART_APPSERVER;
               skip = 3;
            }
            else if (nxt.equals("trim"))
            {
               mode = MODE_TRIM_APPSERVER;
               skip = 3;
            }
         }
      }

      CentralLogger.setMode(mode == MODE_START ? CentralLogger.Mode.SERVER : CentralLogger.Mode.DEFAULT);
      
      // -profile profile-name
      if (args[current].equals("-profile"))
      {
         cfgProfile = args[current + 1];
         skip = 2;
      }
      
      if (skip == 0)
      {
         skip = super.processArguments(args, current);
      }
      
      return skip;
   }
   
   /**
    * Allows a subclass to check if the given configuration matches any
    * required values.  Edits may be made if needed.
    *
    * @param    bc
    *           The configuration to be checked.
    */
   protected void checkConfig(BootstrapConfig bc)
   throws ConfigurationException
   {
      // MODE_START is the server, other modes act like a client node
      bc.setServer((mode == MODE_START));
   }
   
   /**
    * Runs the primary application using the preprocessed configuration and
    * target object.
    * 
    * @param    bc
    *           The configuration to use.
    */
   protected void start(BootstrapConfig bc)
   throws Exception
   {
      SecureSocketsRegistrar.register(bc, false);
      
      switch (mode)
      {
         case MODE_RECONFIGURE:
            // the server must not be running
            int status = statusWorker(bc);
            if (status != STATUS_STOPPED)
            {
               LOG.severe("Make sure the server is not running!");
               return;
            }
            
            // we can put it in server mode now
            bc.setServer(true);
            
            // reconfigure
            SSLCertGenUtil gen = new SSLCertGenUtil(bc);
            gen.generate();
            
            // don't fallback to server mode
            break;
         case MODE_START:
            StandardServer srv = new StandardServer();
            srv.bootstrap(bc, this::printDriverDetails, cfgProfile);
            break;
         case MODE_STOP:
            stop(bc);
            break;
         case MODE_STATUS:
            status(bc);
            break;
         case MODE_START_METHOD_TRACE:
            enableMethodTracing(bc, true);
            break;
         case MODE_STOP_METHOD_TRACE:
            enableMethodTracing(bc, false);
            break;
         case MODE_WAIT:
            wait(bc, timeout);
            break;
         case MODE_START_APPSERVER:
            launchAppServer(bc, appServer);
            break;
         case MODE_RESTART_APPSERVER:
            restartAppServer(bc, appServer);
            break;
         case MODE_TRIM_APPSERVER:
            trimAppServer(bc, appServer);
            break;
         case MODE_START_BATCH_PROCESS:
            launchBatchProcess(bc, batchProcess);
            break;
         default:
            syntax("Unknown mode " + mode, -100);
      }
   }

   /**
    * Make a connection to the specified server and then shut that server 
    * down.  This method will report on the that state by exiting with a
    * predefined exit code in <code>System.exit</code>.
    * <p>
    * <b>This method never returns.</b>
    * 
    * @param    bc
    *           The configuration to use.
    */
   protected void stop(BootstrapConfig bc)
   {
      int result = STATUS_STOPPED;
      
      try
      {
         Session session = connect(bc);
         RoutingKey key = RoutingKey.getSpecial(RoutingKey.SHUTDOWN_PACKAGE);
         
         if (session != null)
         {
            Message msg = new Message(MessageTypes.REQUEST_SYNCH,
                                      key,
                                      new Object[] {});
            
            Message reply = (Message) session.transact(msg, 60000);
            
            if (reply != null && reply.getType() == MessageTypes.REPLY_EXCEPTION_SYNC)
            {
               // shutdown failed
               result = STATUS_RUNNING;
            }
            else
            {
               // null reply or a non-exception means it stopped properly there is no really good way to
               // detect a timeout, so ignore that case
            }
         }
      }
      catch (Exception e)
      {
         LOG.severe("Exception while stopping the remote server:", e);
         result = STATUS_UNKNOWN;
      }
      finally
      {
         System.exit(result);
      }
   }
   
   /**
    * Check on the state of the specified server and report that state by
    * exiting with a predefined exit code in <code>System.exit</code>.
    * <p>
    * <b>This method never returns.</b>
    * 
    * @param    bc
    *           The configuration to use.
    */
   protected void status(BootstrapConfig bc)
   {
      System.exit(statusWorker(bc));
   }
   
   /**
    * Wait until the specified server is in a running state or the specified
    * timeout elapsed, whichever comes first.  This method reports the
    * resulting state by exiting with a predefined exit code in 
    * <code>System.exit</code>.
    * <p>
    * This method uses polling to detect the <code>STATUS_RUNNING</code>
    * state!  It will also exit in the case where the status of the server
    * is <code>STATUS_UNKNOWN</code> or in a timeout case.  As long as the
    * timeout has not occurred and the state is <code>STATUS_STOPPED</code>,
    * this method will wait.
    * <p>
    * <b>This method never returns.</b>
    * 
    * @param    bc
    *           The configuration to use.
    * @param    timeout
    *           Number of milliseconds to wait or -1 for an infinite wait.
    */
   protected void wait(BootstrapConfig bc, int timeout)
   {
      int  result = STATUS_STOPPED;
      long waited = 0;
      long start  = System.currentTimeMillis();
      
      while (timeout == -1 || waited < timeout)
      {
         result = statusWorker(bc);
         
         if (result != STATUS_STOPPED)
         {
            // done
            break;
         }
         
         // setup for possible timeout return on next loop condition test
         result = STATUS_TIMEOUT;
         
         try
         {
            // wait
            Thread.sleep(500);
         }
         
         catch (InterruptedException ie)
         {
            // best efforts only
         }
         
         // calc total time waited 
         waited = System.currentTimeMillis() - start;
      }
      
      System.exit(result);
   }
   
   /**
    * Connect to the remote P2J server and send the command to start the appserver.
    * 
    * @param    config
    *           The P2J server configuration.
    * @param    appServer
    *           The appserver name.
    */
   private void launchAppServer(BootstrapConfig config, String appServer)
   {
      Session session = null;
      try
      {
         session = connect(config);
         
         AppServer launcher = (AppServer) RemoteObject.obtainNetworkInstance(AppServer.class, session);
         launcher.launch(appServer);
      }
      finally
      {
         if (session != null)
         {
            session.terminate();
         }
      }
   }
   
   /**
    * Connect to the remote P2J server and send the command to restart all agents in the appserver.
    * 
    * @param    config
    *           The P2J server configuration.
    * @param    appServer
    *           The appserver name.
    */
   private void restartAppServer(BootstrapConfig config, String appServer)
   {
      Session session = null;
      try
      {
         session = connect(config);
         
         AppServer launcher = (AppServer) RemoteObject.obtainNetworkInstance(AppServer.class, session);
         launcher.restart(appServer);
      }
      finally
      {
         if (session != null)
         {
            session.terminate();
         }
      }
   }
   
   /**
    * Connect to the remote P2J server and send the command to trim the agents in the appserver.
    * 
    * @param    config
    *           The P2J server configuration.
    * @param    appServer
    *           The appserver name.
    */
   private void trimAppServer(BootstrapConfig config, String appServer)
   {
      Session session = null;
      try
      {
         session = connect(config);
         
         AppServer launcher = (AppServer) RemoteObject.obtainNetworkInstance(AppServer.class, session);
         launcher.trim(appServer);
      }
      finally
      {
         if (session != null)
         {
            session.terminate();
         }
      }
   }

   /**
    * Start or stop data collection for method execution tracing running in the separate server
    * process defined by the given bootstrap configuration.
    * 
    * @param   config
    *          The configuration used to connect to the tracing server.
    * @param   enable
    *          {@code true} to start data collection; {@code false} to stop it and process the
    *          collected data.
    */
   protected void enableMethodTracing(BootstrapConfig config, boolean enable)
   {
      Session session = null;
      try
      {
         session = connect(config);
         
         MethodTraceController controller = (MethodTraceController)
            RemoteObject.obtainNetworkInstance(MethodTraceController.class, session);
         controller.enableTracing(enable);
      }
      finally
      {
         if (session != null)
         {
            session.terminate();
         }
      }
   }
   
   /**
    * Connect to the remote P2J server and send the command to start the batch process.
    * 
    * @param    config
    *           The P2J server configuration.
    * @param    batchProcess
    *           The batch process name.
    */
   private void launchBatchProcess(BootstrapConfig config, String batchProcess)
   {
      Session session = null;
      try
      {
         session = connect(config);
         
         BatchProcess launcher = (BatchProcess) RemoteObject.obtainNetworkInstance(BatchProcess.class,
                                                                                   session);
         launcher.launch(batchProcess);
      }
      finally
      {
         if (session != null)
         {
            session.terminate();
         }
      }
   }
   
   /**
    * Check on the state of the specified server and return that state.
    * 
    * @param    bc
    *           The configuration to use.
    *
    * @return   The state of the server.
    */
   private int statusWorker(BootstrapConfig bc)
   {
      int result = STATUS_STOPPED;
      
      try
      {
         Session session = connect(bc);
         
         if (session != null)
         {
            byte[] data = new byte[] { 0 };
            
            try
            {
               // try an echo
               Object reply = session.echo(data, 10000, null);
               result = (reply == null) ? STATUS_TIMEOUT : STATUS_RUNNING;
            }
            
            catch (Exception e)
            {
               result = STATUS_UNKNOWN;
            }
         }
      }
      
      catch (Exception e)
      {
         // default return
      }
      
      return result;
   }
   
   /**
    * Connect to the specified server on the <code>localhost</code>.
    * 
    * @param    bc
    *           The configuration to use.
    *
    * @return   The established connection.
    */
   private Session connect(BootstrapConfig bc)
   {
      Session session = null;
      
      // force config to certain known values
      
      // allow the node address to be dynamically assigned
      bc.setConfigItem("net", "server", "nodeaddress", null);
      
      // reduce the dispatcher threads to a minimum to make this faster
      bc.setConfigItem("net", "dispatcher", "threads", "2");
      
      String host = bc.getString(ConfigItem.SERVER_HOST, null);
      
      // default to localhost if no target host is specified
      if (host == null)
      {
         bc.setConfigItem(ConfigItem.SERVER_HOST, "localhost");
      }
      
      try
      {
         if (bc.getString("security", "keystore", "filename", null) == null)
         {
            // if no keystore is specified, then we need to initialize as "server" and load it
            // from the directory.
            bc.setServer(true);
            bc.setConfigItem("directory", "xml", "must_exist", "true");
            DirectoryService ds = DirectoryService.createInstance(bc);
            if (!ds.bind())
            {
               LOG.warning("Could not bind directory!");
            }
            
            SecurityManager sm = SecurityManager.createInstance(bc);
            
            if (bc.getString("security", "truststore", "alias", null) == null)
            {
               // explictly set the trust alias as the server's alias
               String alias = sm.getServerAlias();
               bc.setConfigItem("security", "truststore", "alias", alias);
            }
            // force loading of the transport security
            sm.contextSm.getSecureSocketContext();
            // go back in non-server mode
            bc.setServer(false);
         }
         else if (SecurityManager.getInstance() == null)
         {
            SecurityManager.createInstance(bc);
         }

         SessionManager sessMgr = SessionManagerFactory.createLeafNode(bc);
         session = sessMgr.connectDirect(bc, null, null);
      }
      
      catch (ConnectException exc)
      {
         // nothing to do;  the server is not running --> just allow null to
         // be returned below
      }
      
      catch (Exception exc)
      {
         LOG.severe("Error connecting to server", exc);
      }
      
      return session;
   }
   
   /**
    * The command line entry point.
    * 
    * @param     args
    *            The array of command-line parameters.
    */
   public static void main(String[] args)
   {
      CentralLogger.setMode(CentralLogger.Mode.SERVER_BASE);
      LOG = CentralLogger.get(ServerDriver.class);

      setDefaultExceptionHandler(LOG);
      
      final Charset charset = Charset.defaultCharset();
      if (!StandardCharsets.UTF_8.equals(charset))
      {
         LOG.warning("The default character set is " + charset
                  + ", but currently only UTF-8 is supported.");
      }
      
      ServerDriver server = new ServerDriver();
      FwdServerJMX.init();
      
      server.process(args);
   }
}