# 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);
       }
    }