# AppServerHelper.java

    /*
    ** Module   : AppServerHelper.java
    ** Abstract : A helper class to ease appserver connection and remote calls.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 CA  20130529 Created initial version.
    ** 002 CA  20130813 Track the mode of the connection (session-free or session-managed). Implement
    **                  async calls.
    ** 003 CA  20140110 Added access to the table metadata received from the remote side.
    ** 004 CA  20140114 Changes required after extending the ServerHelper class.
    ** 005 CA  20140509 Before sending any command to the remote appserver, check if the connection
    **                  was established properly; if not, throw an IllegalStateException.
    ** 006 SVL 20140414 Do not send input data set for output parameters.
    ** 007 SVL 20140611 Fixed TableWrapper processing in postProcessResult.
    ** 008 ECF 20140624 Removed client-side Hibernate dependency.
    ** 009 SVL 20140709 Send table name for input tables. Create new table on the called side if the
    **                  destination table is not specified.
    ** 010 CA  20140813 Fixed an index problem in TableResultSet.nextRow (anon class).
    ** 011 CA  20150416 Added isConnected which, beside checking if the appserver is connected, checks
    **                  if the P2J session is still running.
    **                  Added javadocs related to the unchecked exceptions thrown by the exposed APIs.
    ** 012 CA  20160404 Enhanced the appserver invocations, to allow implicit or explicit termination
    **                  of requests being executing on a certain connection.
    ** 013 ECF 20170817 Added comment.
    ** 014 CA  20181112 Fixed a case of IndexOutOfBounds, when the call failed but the number of
    **                  arguments differs (at the requester and remote side).
    ** 015 CA  20190118 disconnect() must be aware if the FWD session is gone.
    ** 016 CA  20190612 Added APIs to create remote legacy objects and invoke methods on them.
    ** 017 CA  20190628 Fixed issues when executing in-server appserver calls (disconnections and
    **                  passing TABLE parameters).
    **     CA  20190703 Added WebHandler service support.
    ** 018 CA  20190714 Added DATASET support.
    **     CA  20190731 Allow remote side to send and receive data when the parameter is defined as a
    **                  TABLE-HANDLE, thus the TABLE needs to be created at runtime.
    **     CA  20190812 Fixed more cases of FWD appserver requests in the same server, related to
    **                  transporting DATASET and TABLE parameters.
    **                  Added a way to debug the request and response arguments.
    ** 019 CA  20191119 Added support for before-table.
    ** 020 CA  20200427 Added OERequestInfo support.  Fixed memptr transfer.
    **     CA  20200514 Fixed error handling for handleWebService.
    **     CA  20200528 Fixes for extent arguments.
    ** 021 CA  20200827 Reworked asynchronous invocations to perform all context-local work on the Conversation
    **                  thread, and let only the actual invocation be performed in an AssociatedThread.
    ** 022 RFB 20200627 Fix a NPE that occurs downstream by having postProcessResult bypass a null tablename.
    **     CA  20200914 Fixed a problem with single-run procedures (was checking DeferredProgram instead of
    **                  DeferredProgramWrapper).
    **     CA  20201027 The remote side can delete the IN HANDLE used by the requester at the RUN statement.
    **     OM  20210127 Replaced TableMapper.getLegacyName() triple map lookup with direct access to local
    **                  dmoMeta.legacyTable.
    **     SVL 20210202 Assign return value to a CONNECT invocation result only if the value was set during this
    **                  call.
    **     CA  20210304 Fixed an issue with INPUT-OUTPUT DATASET arguments.
    **     SVL 20210517 In preProcessParameters check if the source table is valid.
    **     SVL 20210517 Update to the previous fix: set TableWrapper.tableHandle flag.
    **     SVL 20210614 Remote OUTPUT TABLE/DATASET-HANDLE parameters are treated like INPUT-OUTPUT parameters
    **                  with no input data rows, which allows to access table structure if non-unknown
    **                  table/dataset handle has been passed from the calling side.
    **     SVL 20210616 Fixed copying rows in INPUT direction (regression caused by the previous entry).
    **     SVL 20210701 Fixed abend for remote DataSetContainer/DatasetWrapper parameters. Clear row data for
    **                  remote DataSetContainer/DatasetWrapper/TableResultSet OUTPUT parameters.
    **     CA  20211112 Refactored to allow transfer of the DATASET or TABLE via XML, for SOAP calls.
    **     CA  20211214 For State-free appservers, the agents can be bound to remote persistent procedures; this
    **                  binding must be made using the agent's ID, as a client can invoke multiple remote
    **                  persistent procedures, and using the procedure's ID for this binding can lead to
    **                  collisions, as the pair (connection ID, procedure ID) is not guaranteed to be unique for
    **                  a connection.
    **     CA  20220110 Refactored the temp-table's row-meta state to use constants for the indexes in the array
    **                  used for serialization.
    **     CA  20220208 For calls originating from outside the FWD server, do not serialize the legacy OO errors.
    **                  Added support for legacy OO errors thrown from a remote appserver call, via RETURN ERROR.
    **     CA  20220329 Added support for REST services written directly in Java.
    **     CA  20220405 Added authentication and authorization for web requests.  When this is enabled, the target
    **                  API call will be executed under the authenticated FWD context, and not the agent's context.
    **     TJD 20220504 Java 11 compatibility related minor changes
    **     CA  20220930 InvokeConfig.target is now a String, not a character BDT.
    **     TJD 20220504 Java 11 compatibility related minor changes
    **     CA  20220428 Fixed memptr, extent, table arguments when the request is remote.
    **     CA  20220602 Added basic support for transport of object instances over appserver call.
    **     CA  20220912 If a dataset from the call has no buffers added to it, set it to unknown.
    **     IAS 20221029 Fixed TableParameter processing.
    ** 023 HC  20230116 Replaced some handle usages with the actual wrapped resources for
    **                  performance.
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 024 CA  20230321 Fixed a problem in 'connect' - can't call 'disableLegacyErrors' if the connection failed.
    ** 025 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 026 CA  20230607 Do not serialize requester's handle to the remote side (in JVM call will share the
    **                  instance with the remote side).
    ** 027 CA  20230712 Added 'invokeWithArgs', an API which can send any number of arguments to a remote program
    **                  acting as a controller, which can prepare and execute the real target.
    ** 028 CA  20231020 Only INPUT or INPUT-OUTPUT TableParameter for remote calls will populate something about
    **                  the table.
    **                  Fixed a regression in CA/20230607 - the handle for persistent procedure needs to be set
    **                  again at InvokeConfig.
    ** 029 CA  20240327 Fixed memptr and object serialization for networked appserver calls.
    ** 030 CA  20240426 Track the received dataset container holds a valid dataset.
    ** 031 GBB 20240513 connectLegacyMode to lookupRemoteAppServer only once.
    ** 032 GBB 20240709 Hard-coded config names replaced by ConfigItem constants.
    ** 033 AP  20240802 Improved support for objects being sent as parameters to remote procedures
    **                  inside the same JVM.
    ** 034 CA  20240910 Fixed appserver disconnect on QUIT from remote call.
    ** 035 GBB 20250403 Support connect with a service type param. Support child connection id for MSA.
    **                  Throw excpliticly an error when the connection has timed out (14810).
    ** 036 CA  20250324 Improvements for remote FWD OpenClient connections: ensure that the remote side uses a
    **                  single connection to the FWD server, and also use a client-side pool of worker threads to
    **                  send the requests (the pool size is in sync with the maximum agents available to process
    **                  requests).
    */

    /*
    ** 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.util;

    import java.io.*;
    import java.lang.reflect.Array;
    import java.net.*;
    import java.sql.*;
    import java.util.concurrent.atomic.*;

    import javax.servlet.http.*;

    import com.goldencode.p2j.*;
    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.main.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.oo.lang.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.hql.*;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Provides a collection of APIs for connecting to the appserver and performing remote calls.
     * <p>
     * When the {@link #connect} or {@link #connectLegacyMode} is called, an instance of this class is
     * returned which contains the connection ID and can be used to execute remote calls, via the
     * determined {@link #appServer} network server.
     * <p>
     * When establishing connections from Java code, use the {@link #connect}, {@link #invoke} and
     * {@link #invokePersistent} APIs to establish a connection and send requests to the remote side.
     * The {@link #getConnectError()}, {@link #getConnectResult()} and {@link #getConnectionID()} APIs
     * provide information about the state of the last CONNECT request; note that a connection ID will
     * be sent back only if the connection was successful.
     * <p>
     * This connection established via instances of this class is not thread-safe - the client can either pool
     * the connections and check them out as needed, or otherwise ensure that the connection is used on only one
     * thread (this includes the {@link #connect}, invocations and {@link #disconnect} calls.
     */
    public class AppServerHelper
    extends ServerHelper
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(AppServerHelper.class);

       /** Flag indicating if the request/response are to be dumped to files. */
       private static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("DUMP_APPSERVER_DATA"));

       /** A counter to ensure the debugged request/response file names are unique. */
       private static final AtomicLong COUNTER = (DEBUG ? new AtomicLong(0) : null);

       /**
        * A local appserver instance, used when the P2J server running the appserver is the same as
        * the P2J server from where the connection request originates.
        */
       private static AppServerEntry localAppServer = null;

       /** The id of this appserver connection. */
       private final String connectionID;

       /** Flag indicating this helper has disconnected. */
       private boolean disconnected = false;

       /** The name of the connected appserver. */
       private final String appServerName;

       /**
        * Flag indicating if this connection is in session free mode (if true) or in session-managed
        * mode (if false).
        */
       private final boolean sessionFree;

       /**
        * A network server to the P2J server running this appserver. Each call via this server
        * (except the connect-related calls) will have the connection ID as the first parameter.
        */
       private final AppServerEntry appServer;

       /** The result returned by this CONNECT call. */
       private String connectResult = null;

       /** The error encountered during the CONNECT call. */
       private NumberedException connectError = null;

       /**
        * The P2J session used to communicate with the remote side. May be <code>null</code> if the
        * appserver is in the same JVM as the requester. */
       private Session session = null;

       /** Flag indicating the appserver is running in the same JVM as the requester. */
       private boolean local = false;

       /**
        * The result from the last remote invocation.  This is used only for {@link #connect}, to
        * restore the {@link OerequestInfo} response.
        */
       private AppServerInvocationResult lastResult = null;

       /** Flag indicating the legacy errors are disabled. */
       private boolean disabledLegacyErrors = false;

       /** Flag indicating if this instance is shared by multiple connections/threads. */
       private boolean shared = false;

       /**
        * Initialize this helper when the connection was not established.
        */
       private AppServerHelper()
       {
          this(null, null, null, null, false);
       }

       /**
        * Instantiate a new helper to communicate with the specified appServer instance.
        *
        * @param    appServerName
        *           The name of the connected appserver.
        * @param    connectionID
        *           The ID for this appServer connection.
        * @param    session
        *           The P2J session used to communicate with the remote side. May be <code>null</code>
        *           if the appServer is on the same P2J Server as the requester.
        * @param    appServer
        *           The {@link AppServerEntry} network server instance.
        */
       private AppServerHelper(String         appServerName,
                               String         connectionID,
                               Session        session,
                               AppServerEntry appServer,
                               boolean        sessionFree)
       {
          this.appServerName = appServerName;
          this.connectionID = connectionID;
          this.session = session;
          this.appServer = appServer;
          this.sessionFree = sessionFree;
       }

       /**
        * Authenticate with the given P2J server and establish an appserver connection.  If the
        * address is the address of this running P2J server, then a local proxy is resolved and no
        * P2J connection is established.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The P2J account to authenticate on the remote side.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connect(BootstrapConfig  config,
                                             String           account,
                                             String           appServerName,
                                             boolean          sessionFree)
       {
          return connect(config, account, null, null, appServerName, sessionFree, null, null, null);
       }

       /**
        * Authenticate with the given P2J server and establish an appserver connection.  If the
        * address is the address of this running P2J server, then a local proxy is resolved and no
        * P2J connection is established.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The P2J account to authenticate on the remote side.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        * @param    webServiceType
        *           The type of web service the worker is used for.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connect(BootstrapConfig  config,
                                             String           account,
                                             String           appServerName,
                                             boolean          sessionFree,
                                             String           webServiceType)
       {
          return connect(config, account, null, null, appServerName, sessionFree, null, null, null,
                         null, webServiceType);
       }

       /**
        * Authenticate with the given P2J server and establish an appserver connection.  If the
        * address is the address of this running P2J server, then a local proxy is resolved and no
        * P2J connection is established.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The P2J account to authenticate on the remote side.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        * @param    user
        *           The username passed to the appserver's connect procedure.
        * @param    pwd
        *           The password passed to the appserver's connect procedure.
        * @param    serverInfo
        *           The info passed to the appserver's connect procedure.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connect(BootstrapConfig   config,
                                             String            account,
                                             String            appServerName,
                                             boolean           sessionFree,
                                             String            user,
                                             String            pwd,
                                             String            serverInfo)
       throws NumberedException
       {
          return connect(config, account, null, null,
                         appServerName, sessionFree, user, pwd, serverInfo);
       }

       /**
        * Authenticate with the given P2J server and establish an appserver connection.  If the
        * address is the address of this running P2J server, then a local proxy is resolved and no
        * P2J connection is established.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The P2J account to authenticate on the remote side.
        * @param    listener
        *           The session listener, used when a virtual or direct session is established.
        * @param    handler
        *           The interrupt handler, used when a direct session is established.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connect(BootstrapConfig  config,
                                             String           account,
                                             SessionListener  listener,
                                             InterruptHandler handler,
                                             String           appServerName,
                                             boolean          sessionFree)
       {
          return connect(config, account, listener, handler, appServerName, sessionFree,
                         null, null, null);
       }

       /**
        * Authenticate with the given FWD server and establish an appserver connection.  If the
        * address is the address of this running FWD server, then a local proxy is resolved and no
        * FWD connection is established.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The FWD account to authenticate on the remote side.
        * @param    listener
        *           The session listener, used when a virtual or direct session is established.
        * @param    handler
        *           The interrupt handler, used when a direct session is established.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        * @param    user
        *           The username passed to the appserver's connect procedure.
        * @param    pwd
        *           The password passed to the appserver's connect procedure.
        * @param    serverInfo
        *           The info passed to the appserver's connect procedure.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connect(BootstrapConfig  config,
                                             String           account,
                                             SessionListener  listener,
                                             InterruptHandler handler,
                                             String           appServerName,
                                             boolean          sessionFree,
                                             String           user,
                                             String           pwd,
                                             String           serverInfo)
       {
          return connect(config,
                         account,
                         listener,
                         handler,
                         appServerName,
                         sessionFree,
                         user,
                         pwd,
                         serverInfo,
                         null);
       }

       /**
        * Authenticate with the given P2J server and establish an appserver connection.  If the
        * address is the address of this running P2J server, then a local proxy is resolved and no
        * P2J connection is established.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The P2J account to authenticate on the remote side.
        * @param    listener
        *           The session listener, used when a virtual or direct session is established.
        * @param    handler
        *           The interrupt handler, used when a direct session is established.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        * @param    user
        *           The username passed to the appserver's connect procedure.
        * @param    pwd
        *           The password passed to the appserver's connect procedure.
        * @param    serverInfo
        *           The info passed to the appserver's connect procedure.
        * @param    requestInfo
        *           The remote {@link com.goldencode.p2j.oo.lang.OerequestInfo} details, as set by
        *           {@link com.goldencode.p2j.oo.lang.OerequestInfo#toArray()}.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connect(BootstrapConfig  config,
                                             String           account,
                                             SessionListener  listener,
                                             InterruptHandler handler,
                                             String           appServerName,
                                             boolean          sessionFree,
                                             String           user,
                                             String           pwd,
                                             String           serverInfo,
                                             Object[]         requestInfo)
       {
          return connect(config, account, listener, handler, appServerName, sessionFree, user, pwd, serverInfo,
                         requestInfo, null);
       }

       /**
        * Connect to the FWD server, as specified in the given configuration.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The FWD account to authenticate on the remote side.
        * @param    listener
        *           The session listener, used when a virtual or direct session is established.
        * @param    handler
        *           The interrupt handler, used when a direct session is established.
        *
        * @return   A record with the FWD server connection details.
        */
       public static RemoteServerInfo connectToServer(BootstrapConfig  config,
                                                      String           account,
                                                      SessionListener  listener,
                                                      InterruptHandler handler)
       {
          int port = -1;
          boolean isSecure = config.getBoolean(ConfigItem.SECURE, false);
          if (isSecure)
          {
             port = config.getInt(ConfigItem.SERVER_SECURE_PORT, -1);
          }
          else
          {
             port = config.getInt(ConfigItem.SERVER_PORT, -1);
             if (port == -1)
             {
                port = config.getInt(ConfigItem.SERVER_INSECURE_PORT, -1);
             }
          }
          String host = config.getString(ConfigItem.SERVER_HOST, "localhost");
          InetSocketAddress remoteAddr = new InetSocketAddress(host, port);

          // when connecting, ignore all SSL-related options from the 4GL code, as they are
          // irrelevant
          Session session = null;
          SessionManager sessMgr = SessionManager.get();
          boolean local = false;

          if (sessMgr.isLeaf())
          {
             // this is a call originating from outside P2J code

             try
             {
                // establish a direct connection
                session = sessMgr.connectDirect(config, handler, listener);
             }
             catch (Exception e)
             {
                // the connection could not be established
                return new RemoteServerInfo(new NumberedException("Connection refused by Application Server",
                                                                  5467, e));
             }
          }
          else
          {
             local = sessMgr.isLocalNode(remoteAddr);
             if (!local)
             {
                try
                {
                   // establish a virtual connection
                   session = sessMgr.connectVirtual(remoteAddr, null, listener, account);
                }
                catch (Exception e)
                {
                   // the connection could not be established
                   return new RemoteServerInfo(new NumberedException("Connection refused by Application Server",
                                                                     5467, e));
                }
             }
          }

          AppServerEntry appServer = null;
          if (session != null)
          {
             appServer = (AppServerEntry) RemoteObject.obtainNetworkInstance(AppServerEntry.class, session);
          }
          else
          {
             appServer = getLocalAppServer();
          }

          return new RemoteServerInfo(appServer, session, local);
       }

       /**
        * Authenticate with the given FWD server and establish an appserver connection.  If the
        * address is the address of this running FWD server, then a local proxy is resolved and no
        * FWD connection is established.
        *
        * @param    server
        *           The FWD server details.
        * @param    shared
        *           Flag indicating if the FWD server connection will be shared by separate clients connecting to
        *           an appserver.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        * @param    user
        *           The username passed to the appserver's connect procedure.
        * @param    pwd
        *           The password passed to the appserver's connect procedure.
        * @param    serverInfo
        *           The info passed to the appserver's connect procedure.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connectToServer(RemoteServerInfo server,
                                                     boolean          shared,
                                                     String           appServerName,
                                                     boolean          sessionFree,
                                                     String           user,
                                                     String           pwd,
                                                     String           serverInfo)
       {
          return connectToServer(server, shared, appServerName, sessionFree, user, pwd, serverInfo, null, null);
       }

       /**
        * Authenticate with the given FWD server and establish an appserver connection.  If the
        * address is the address of this running FWD server, then a local proxy is resolved and no
        * FWD connection is established.
        *
        * @param    server
        *           The FWD server details.
        * @param    shared
        *           Flag indicating if the FWD server connection will be shared by separate clients connecting to
        *           an appserver.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        * @param    user
        *           The username passed to the appserver's connect procedure.
        * @param    pwd
        *           The password passed to the appserver's connect procedure.
        * @param    serverInfo
        *           The info passed to the appserver's connect procedure.
        * @param    requestInfo
        *           The remote {@link com.goldencode.p2j.oo.lang.OerequestInfo} details, as set by
        *           {@link com.goldencode.p2j.oo.lang.OerequestInfo#toArray()}.
        * @param    webServiceType
        *           The type of web service the worker is used for.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       private static AppServerHelper connectToServer(RemoteServerInfo server,
                                                      boolean          shared,
                                                      String           appServerName,
                                                      boolean          sessionFree,
                                                      String           user,
                                                      String           pwd,
                                                      String           serverInfo,
                                                      Object[]         requestInfo,
                                                      String           webServiceType)
       {
          // the P2J account needs to be sent, as in local-proxy mode, a new context/authentication
          // is not performed, and all appserver requests would be sent from the current account.
          AppServerInvocationResult result = server.appServer.connect(appServerName,
                                                                      sessionFree,
                                                                      user,
                                                                      pwd,
                                                                      serverInfo,
                                                                      requestInfo,
                                                                      webServiceType);

          String connectionID = result.getConnectionID();
          AppServerHelper helper = new AppServerHelper(appServerName,
                                                       connectionID,
                                                       server.session,
                                                       server.appServer,
                                                       sessionFree);
          helper.shared = shared;
          if (server.session != null && connectionID != null)
          {
             // we are not in local appserver mode, disable legacy errors
             helper.disableLegacyErrors();
          }

          helper.local = server.local;
          helper.lastResult = result;

          if (result.getResult() != null)
          {
             helper.setConnectResult(new character((BaseDataType) result.getResult()));
          }

          if (result.getError() != null || connectionID == null)
          {
             // the connection could not be established
             helper.setConnectError(new NumberedException("Connection refused by Application Server",
                                                          5467, result.getError()));
          }

          return helper;
       }

       /**
        * Authenticate with the given P2J server and establish an appserver connection.  If the
        * address is the address of this running P2J server, then a local proxy is resolved and no
        * P2J connection is established.
        *
        * @param    config
        *           The configuration for the P2J server connection.
        * @param    account
        *           The P2J account to authenticate on the remote side.
        * @param    listener
        *           The session listener, used when a virtual or direct session is established.
        * @param    handler
        *           The interrupt handler, used when a direct session is established.
        * @param    appServerName
        *           The name of the appserver to which the connection will be established.
        * @param    sessionFree
        *           <code>true</code> to indicate a session-free operating mode, <code>false</code>
        *           to indicate a session-managed operating mode.
        * @param    user
        *           The username passed to the appserver's connect procedure.
        * @param    pwd
        *           The password passed to the appserver's connect procedure.
        * @param    serverInfo
        *           The info passed to the appserver's connect procedure.
        * @param    requestInfo
        *           The remote {@link com.goldencode.p2j.oo.lang.OerequestInfo} details, as set by
        *           {@link com.goldencode.p2j.oo.lang.OerequestInfo#toArray()}.
        * @param    webServiceType
        *           The type of web service the worker is used for.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       public static AppServerHelper connect(BootstrapConfig  config,
                                             String           account,
                                             SessionListener  listener,
                                             InterruptHandler handler,
                                             String           appServerName,
                                             boolean          sessionFree,
                                             String           user,
                                             String           pwd,
                                             String           serverInfo,
                                             Object[]         requestInfo,
                                             String           webServiceType)
       {
          RemoteServerInfo server = connectToServer(config, account, listener, handler);

          if (server.connectError != null)
          {
             AppServerHelper helper = new AppServerHelper();
             helper.setConnectError(server.connectError);
             return helper;
          }

          return connectToServer(server, false, appServerName, sessionFree, user, pwd, serverInfo, requestInfo,
                                 webServiceType);
       }

       /**
        * This code is intended for legacy compatibility, and is used to connect to an appserver, when
        * a CONNECT statement is encountered.
        * <p>
        * This will use the known appservers configured in the directory to identify the P2J server
        * and account, on which the appserver targeted by the CONNECT statement is running.
        *
        * @param    appService
        *           The value of the "-AppService" parameter.
        * @param    host
        *           The value of the "-H" parameter.
        * @param    serviceName
        *           The service name specified by the "-S" parameter.
        * @param    port
        *           The port specified by the "-S" parameter.
        * @param    sessionModel
        *           The session mode: session-free or session-managed.
        * @param    user
        *           The username passed to the appserver's connect procedure.
        * @param    pwd
        *           The password passed to the appserver's connect procedure.
        * @param    info
        *           The info passed to the appserver's connect procedure.
        * @param    request
        *           The remote {@link OerequestInfo} details, as set by
        *           {@link OerequestInfo#toArray()}.
        *
        * @return   The helper with the connection result (which was established or not).
        */
       static AppServerHelper connectLegacyMode(String    appService,
                                                String    host,
                                                String    serviceName,
                                                int       port,
                                                boolean   directConnect,
                                                String    sessionModel,
                                                character user,
                                                character pwd,
                                                character info,
                                                Object[]  request)
       throws NumberedException
       {
          boolean sessionFree = AppServerSessionModel.SESSION_FREE.toString().equalsIgnoreCase(sessionModel);
          AppServerManager.RemoteAppServer remoteAppServer = AppServerManager.lookupRemoteAppServer(appService,
                                                                                                    host,
                                                                                                    serviceName,
                                                                                                    port,
                                                                                                    directConnect,
                                                                                                    sessionModel);
          if (remoteAppServer == null)
          {
             return null;
          }

          InetSocketAddress address = new InetSocketAddress(remoteAppServer.p2jHost, remoteAppServer.p2jPort);

          if (address.getAddress() == null)
          {
             throw new NumberedException(String.format("Connect failure for host %s port %s transport TCP",
                                                       host,
                                                       serviceName == null ? Integer.toString(port) : serviceName),
                                         9407);
          }

          String sUser = (user == null || user.isUnknown() ? null : user.toStringMessage());
          String sPwd  = (pwd  == null || pwd.isUnknown()  ? null : pwd.toStringMessage());
          String sInfo = (pwd  == null || info.isUnknown() ? null : info.toStringMessage());

          BootstrapConfig config = null;
          try
          {
             config = new BootstrapConfig();
             config.setConfigItem(ConfigItem.SECURE, "true");
             config.setConfigItem(ConfigItem.SERVER_HOST, address.getHostName());
             config.setConfigItem(ConfigItem.SERVER_SECURE_PORT, Integer.toString(address.getPort()));
          }
          catch (ConfigurationException e)
          {
             // this should never happen, but send a STOP in case it does
             throw new StopConditionException(e);
          }

          AppServerHelper helper = connect(config, remoteAppServer.p2jAccount, null, null,
                                           remoteAppServer.name, sessionFree, sUser, sPwd, sInfo);

          // set the value returned by the CONNECT call.
          character result = helper.getConnectResult();
          if (result != null)
          {
             ControlFlowOps.setReturnValue(result);
          }

          if (helper.getConnectError() != null)
          {
             // if there was an error, trhow it here.
             throw helper.getConnectError();
          }

          return helper;
       }

       /**
        * Get the appserver export local to the P2J server.  If not already created, obtain a local
        * instance by calling {@link RemoteObject#obtainLocalInstance}.
        *
        * @return   The local appserver instance.
        */
       private static synchronized AppServerEntry getLocalAppServer()
       {
          if (localAppServer == null)
          {
             localAppServer = (AppServerEntry) RemoteObject.obtainLocalInstance(AppServerEntry.class,
                                                                                true);
          }

          return localAppServer;
       }

       /**
        * Disable legacy OO errors thrown on this connection.
        */
       public void disableLegacyErrors()
       {
          checkConnected();

          disabledLegacyErrors = true;
          appServer.disableLegacyErrors(connectionID);
       }

       /**
        * Get the sub-type of this server.
        *
        * @return   Always the <code>"APPSERVER"</code> string.
        */
       @Override
       public String getSubType()
       {
          return "APPSERVER";
       }

       /**
        * Send a STOP condition to the specified async request.
        *
        * @param    requestId
        *           The request where to send a STOP condition.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       @Override
       public void sendStop(int requestId)
       {
          checkConnected();

          appServer.sendStop(connectionID, requestId);
       }

       /**
        * Send a STOP condition to the request currently being executed on this connection.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       @Override
       public void sendStop()
       {
          checkConnected();

          appServer.sendStop(connectionID);
       }

       /**
        * Check if this connection is in session-free mode or in session-managed mode.
        *
        * @return   The state of the {@link #sessionFree} flag.
        */
       @Override
       public boolean isSessionFree()
       {
          return sessionFree;
       }

       /**
        * Get the ID of this appserver connection.
        *
        * @return   See above.
        */
       @Override
       public String getConnectionID()
       {
          return connectionID;
       }

       /**
        * Check if this is a web service.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       public boolean isWebService()
       {
          return false;
       }

       /**
        * Disconnect from the appserver and terminate the associated P2J session.
        *
        * @param    disconnecting
        *           Flag indicating if the FWD client session is being terminated.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       @Override
       public void disconnect(boolean disconnecting)
       {
          checkConnected();

          if (session == null || session.isRunning())
          {
             appServer.disconnect(connectionID);
          }

          disconnectedImpl();
       }

       /**
        * Explicitly disconnect the server, without invoking the remote {@link AppServerEntry#disconnect}, as
        * the disconnect was from a remote QUIT condition.
        */
       public void disconnectedImpl()
       {
          if (disconnected)
          {
             return;
          }

          if (session != null)
          {
             if (!shared)
             {
                // terminate the session
                session.terminate();
             }

             session = null;
          }

          disconnected = true;
       }

       /**
        * Check if the connection is still established.
        * <p>
        * This includes determining if the {@link #session} is still running and if the appserver
        * connection was established.
        *
        * @return   <code>true</code> if the server is currently connected.
        */
       @Override
       public boolean isConnected()
       {
          return !disconnected                                        &&
                 (local || (session != null && session.isRunning()))  &&
                 connectionID != null                                 &&
                 connectError == null;
       }

       /**
        * Get the name of the remote procedure, identified by the given handle.
        *
        * @param    proc
        *           The handle of the remote procedure.
        *
        * @return   The procedure's name.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected or the handle is not for a proxy procedure.
        */
       public character getProcedureName(handle proc)
       {
          if (!proc._isValid())
          {
             return new character();
          }

          if (proc.getResource() instanceof ProxyProcedureWrapper)
          {
             ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) proc.getResource();
             return getProcedureName(proxy.getResourceId(), proxy.getAgentId());
          }
          else
          {
             throw new IllegalStateException("The handle is not for a proxy procedure!");
          }
       }

       /**
        * Get the name of the remote procedure, identified by the given proxy procedure.
        *
        * @param    proc
        *           The proxy of the remote procedure.
        *
        * @return   The procedure's name.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       public character getProcedureName(ProxyProcedureWrapper proc)
       {
          return getProcedureName(proc.getResourceId(), proc.getAgentId());
       }

       /**
        * Get the name of the remote procedure, identified by the given code.
        *
        * @param    code
        *           The handle code of the remote procedure.
        *
        * @return   The procedure's name.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       public character getProcedureName(String code)
       {
          return getProcedureName(code, -1);
       }

       /**
        * Get the name of the remote procedure, identified by the given code.
        *
        * @param    code
        *           The handle code of the remote procedure.
        * @param    agentId
        *           The agent ID which created the procedure.
        *
        * @return   The procedure's name.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected or an agent ID was not specified for {@link #sessionFree}
        *           mode.
        */
       public character getProcedureName(String code, int agentId)
       {
          checkConnected();

          if (sessionFree && agentId < 0)
          {
             throw new IllegalStateException("The agent ID must be passed in Session-free mode!");
          }

          return appServer.getProcedureName(connectionID, code, agentId);
       }

       /**
        * Delete the associated remote procedure, from the remote side.
        *
        * @param    proc
        *           The remote procedure.
        *
        * @return   <code>true</code> if the remote procedure was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected or the handle is not for a proxy procedure.
        */
       public boolean deleteProcedure(handle proc)
       {
          if (!proc._isValid())
          {
             return false;
          }

          if (proc.getResource() instanceof ProxyProcedureWrapper)
          {
             ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) proc.getResource();
             return deleteProcedure(proxy.getResourceId(), proxy.getAgentId(), null);
          }
          else
          {
             throw new IllegalStateException("The handle is not for a proxy procedure!");
          }
       }

       /**
        * Delete the associated remote procedure, from the remote side.
        *
        * @param    proc
        *           The remote procedure.
        *
        * @return   <code>true</code> if the remote procedure was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       public boolean deleteProcedure(ProxyProcedureWrapper proc)
       {
          return deleteProcedure(proc.getResourceId(), proc.getAgentId(), null);
       }

       /**
        * Delete the associated remote procedure, from the remote side.
        *
        * @param    procId
        *           The remote procedure ID.
        *
        * @return   <code>true</code> if the remote procedure was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       public boolean deleteProcedure(String procId)
       {
          return deleteProcedure(procId, -1, null);
       }

       /**
        * Delete the associated remote procedure, from the remote side.
        *
        * @param    procId
        *           The remote procedure ID.
        * @param    agentId
        *           The remote agent ID.
        * @param    token
        *           When not null, it represents the token of a FWD context created for an authenticated and
        *           authorized web service call.  The API call will be performed in that context.
        *
        * @return   <code>true</code> if the remote procedure was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected or an agent ID was not specified for {@link #sessionFree}
        *           mode.
        */
       public boolean deleteProcedure(String procId, int agentId, String token)
       {
          checkConnected();

          if (sessionFree && agentId < 0)
          {
             throw new IllegalStateException("The agent ID must be passed in Session-free mode!");
          }

          return appServer.deleteProcedure(connectionID, procId, agentId, token);
       }

       /**
        * Delete the associated remote object, from the remote side.
        *
        * @param    proc
        *           The proxy for the remote procedure.
        *
        * @return   <code>true</code> if the remote object was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected or if the handle is not for a proxy procedure.
        */
       public boolean deleteObject(handle proc)
       {
          if (!proc._isValid())
          {
             return false;
          }

          if (proc.getResource() instanceof ProxyProcedureWrapper)
          {
             ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) proc.getResource();
             return deleteObject(proxy.getResourceId(), proxy.getAgentId());
          }
          else
          {
             throw new IllegalStateException("The handle is not for a proxy procedure!");
          }
       }

       /**
        * Delete the associated remote object, from the remote side.
        *
        * @param    proc
        *           The proxy for the remote procedure.
        *
        * @return   <code>true</code> if the remote object was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       public boolean deleteObject(ProxyProcedureWrapper proc)
       {
          return deleteObject(proc.getResourceId(), proc.getAgentId());
       }

       /**
        * Delete the associated remote object, from the remote side.
        *
        * @param    code
        *           The handle code of the remote object.
        *
        * @return   <code>true</code> if the remote object was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       public boolean deleteObject(String code)
       {
          return deleteObject(code, -1);
       }

       /**
        * Delete the associated remote object, from the remote side.
        *
        * @param    code
        *           The handle code of the remote object.
        * @param    agentId
        *           The remote agent ID.
        *
        * @return   <code>true</code> if the remote object was deleted.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected or an agent ID was not specified for {@link #sessionFree}
        *           mode.
        */
       public boolean deleteObject(String code, int agentId)
       {
          checkConnected();

          if (sessionFree && agentId < 0)
          {
             throw new IllegalStateException("The agent ID must be passed in Session-free mode!");
          }

          return appServer.deleteObject(connectionID, code, agentId);
       }

       /**
        * Create a new instance of the specified qualified class name.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy qualified class name for the legacy class.
        * @param    h
        *           The handle where to save the proxy object.
        * @param    args
        *           The constructor's arguments.
        */
       public void newInstance(long      timeout,
                               character name,
                               handle    h,
                               Object... args)
       {
          newInstance(timeout, name, h, (String) null, args);
       }

       /**
        * Create a new instance of the specified qualified class name.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy qualified class name for the legacy class.
        * @param    h
        *           The handle where to save the proxy object.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The constructor's arguments.
        */
       public void newInstance(long      timeout,
                               character name,
                               handle    h,
                               String    modes,
                               Object... args)
       {
          checkConnected();

          // preprocess the parameters and save them in a distinct array, to be sent to the remote
          // side
          Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);

          // obtain an unique ID for this request
          int reqId = appServer.uniqueId();
          AppServerInvocationResult result = appServer.newInstance(connectionID,
                                                                   reqId,
                                                                   timeout,
                                                                   name,
                                                                   modes,
                                                                   preprocessed);

          checkResult(name.toStringMessage(), args, result);

          ProxyProcedureWrapper p = new ProxyProcedureWrapper(result.getProcedureId(), name, result.getAgentId());
          p.setInitialized(true);
          h.assign(p);
       }

       /**
        * Create a new instance of the specified qualified class name.
        *
        * @param    name
        *           The legacy qualified class name for the legacy class.
        * @param    h
        *           The handle where to save the proxy object.
        * @param    args
        *           The constructor's arguments.
        */
       public void newInstance(character name,
                               handle    h,
                               Object... args)
       {
          newInstance(0, name, h, (String) null, args);
       }

       /**
        * Create a new instance of the specified qualified class name.
        *
        * @param    name
        *           The legacy qualified class name for the legacy class.
        * @param    h
        *           The handle where to save the proxy object.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The constructor's arguments.
        */
       public void newInstance(character name,
                               handle    h,
                               String    modes,
                               Object... args)
       {
          newInstance(0, name, h, modes, args);
       }

       /**
        * Invoke a method in a remotely instantiated class.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    returnValue
        *           Flag indicating if the return value is required.
        * @param    name
        *           The legacy 4GL name for the class method.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure or function.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    args
        *           The method's arguments.
        *
        * @return   The result of this method invocation, or <code>null</code> if this is a void
        *           method.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeMethod(long      timeout,
                                        boolean   returnValue,
                                        character name,
                                        handle    h,
                                        Object... args)
       {
          return invokeMethod(timeout, returnValue, name, h, (String) null, args);
       }

       /**
        * Invoke a method in a remotely instantiated class.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    returnValue
        *           Flag indicating if the return value is required.
        * @param    name
        *           The legacy 4GL name for the class method.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure or function.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The method's arguments.
        *
        * @return   The result of this method invocation, or <code>null</code> if this is a void
        *           method.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeMethod(long      timeout,
                                        boolean   returnValue,
                                        character name,
                                        handle    h,
                                        String    modes,
                                        Object... args)
       {
          checkConnected();

          if (!ProcedureManager.isProxy(h))
          {
             throw new IllegalArgumentException("The passed handle is not a proxy procedure!");
          }

          ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) h.getResource();
          String pCode = proxy.getResourceId();
          int agentId = proxy.getAgentId();

          // preprocess the parameters and save them in a distinct array, to be sent to the remote
          // side
          Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);

          // obtain an unique ID for this request
          int reqId = appServer.uniqueId();
          AppServerInvocationResult result = appServer.invokeMethod(connectionID, reqId, timeout,
                                                                    returnValue, name, pCode, agentId,
                                                                    modes, preprocessed);
          checkResult(name.toStringMessage(), args, result);

          return (BaseDataType) result.getResult();
       }

       /**
        * Invoke a method in a remotely instantiated class.
        *
        * @param    returnValue
        *           Flag indicating if the return value is required.
        * @param    name
        *           The legacy 4GL name for the class method.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure or function.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    args
        *           The method's arguments.
        *
        * @return   The result of this method invocation, or <code>null</code> if this is a void
        *           method.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeMethod(boolean   returnValue,
                                        character name,
                                        handle    h,
                                        Object... args)
       {
          return invokeMethod(0, returnValue, name, h, (String) null, args);
       }

       /**
        * Invoke a method in a remotely instantiated class.
        *
        * @param    returnValue
        *           Flag indicating if the return value is required.
        * @param    name
        *           The legacy 4GL name for the class method.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure or function.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The method's arguments.
        *
        * @return   The result of this method invocation, or <code>null</code> if this is a void
        *           method.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeMethod(boolean   returnValue,
                                        character name,
                                        handle    h,
                                        String    modes,
                                        Object... args)
       {
          return invokeMethod(0, returnValue, name, h, modes, args);
       }

       /**
        * Invoke a remote external procedure as persistent. The validation of the legacy program name
        * and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The handle where to save the proxy procedure. May be <code>null</code>.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure/function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invokePersistent(character name,
                                         handle    h,
                                         boolean   transactionDistinct,
                                         Object... args)
       {
          return invokePersistent(name, h, transactionDistinct, null, args);
       }

       /**
        * Invoke a remote external procedure as persistent. The validation of the legacy program name
        * and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The handle where to save the proxy procedure. May be <code>null</code>.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invokePersistent(character name,
                                         handle    h,
                                         boolean   transactionDistinct,
                                         String    modes,
                                         Object... args)
       {
          return invokePersistent(0, name, h, transactionDistinct, modes, args);
       }

       /**
        * Invoke a remote external procedure as persistent. The validation of the legacy program name
        * and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The handle where to save the proxy procedure. May be <code>null</code>.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure/function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invokePersistent(long      timeout,
                                         character name,
                                         handle    h,
                                         boolean   transactionDistinct,
                                         Object... args)
       {
          return invokePersistent(timeout, name, h, transactionDistinct, null, args);
       }

       /**
        * Invoke a remote external procedure as persistent. The validation of the legacy program name
        * and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The handle where to save the proxy procedure. May be <code>null</code>.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invokePersistent(long      timeout,
                                         character name,
                                         handle    h,
                                         boolean   transactionDistinct,
                                         String    modes,
                                         Object... args)
       {

          checkConnected();

          // preprocess the parameters and save them in a distinct array, to be sent to the remote
          // side
          Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);

          // obtain an unique ID for this request
          int reqId = appServer.uniqueId();
          AppServerInvocationResult result = appServer.invokePersistent(connectionID,
                                                                        reqId,
                                                                        timeout,
                                                                        name,
                                                                        transactionDistinct,
                                                                        modes,
                                                                        preprocessed);
          checkResult(name.toStringMessage(), args, result);

          if (h != null)
          {
             String procId = result.getProcedureId();
             int agentId = result.getAgentId();
             character fileName = getProcedureName(procId, agentId);
             ProxyProcedureWrapper p = new ProxyProcedureWrapper(procId, fileName, agentId);
             p.setInitialized(true);
             h.assign(p);
          }

          return new character((BaseDataType) result.getResult());
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(character name,
                                          handle    h,
                                          boolean   transactionDistinct,
                                          Object... args)
       {
          return invoke(name, h, true, false, false, transactionDistinct, null, args);
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(character name,
                                          handle    h,
                                          boolean   transactionDistinct,
                                          String    modes,
                                          Object... args)
       {
          return invoke(name, h, true, false, false, transactionDistinct, modes, args);
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(character name,
                                          handle    h,
                                          boolean   dynamicFunction,
                                          boolean   transactionDistinct,
                                          Object... args)
       {
          return invoke(name, h, true, dynamicFunction, false, transactionDistinct, null, args);
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(character name,
                                          handle    h,
                                          boolean   dynamicFunction,
                                          boolean   transactionDistinct,
                                          String    modes,
                                          Object... args)
       {
          return invoke(name, h, true, dynamicFunction, false, transactionDistinct, modes, args);
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(long      timeout,
                                          character name,
                                          handle    h,
                                          boolean   transactionDistinct,
                                          Object... args)
       {
          return invoke(timeout, name, h, true, false, false, transactionDistinct, null, args);
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(long      timeout,
                                          character name,
                                          handle    h,
                                          boolean   transactionDistinct,
                                          String    modes,
                                          Object... args)
       {
          return invoke(timeout, name, h,
                        true, false, false,
                        transactionDistinct, modes, args);
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(long      timeout,
                                          character name,
                                          handle    h,
                                          boolean   dynamicFunction,
                                          boolean   transactionDistinct,
                                          Object... args)
       {
          return invoke(timeout, name, h,
                        true, dynamicFunction, false,
                        transactionDistinct, null, args);
       }

       /**
        * Invoke a remote remote user-defined function, using the given proxy procedure. The
        * validation for the function name and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the procedure or function.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invokeFunction(long      timeout,
                                          character name,
                                          handle    h,
                                          boolean   dynamicFunction,
                                          boolean   transactionDistinct,
                                          String    modes,
                                          Object... args)
       {
          return invoke(timeout, name, h,
                        true, dynamicFunction, false,
                        transactionDistinct, modes, args);
       }

       /**
        * Invoke a remote external procedure or internal procedure. The validation of the legacy name
        * and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invoke(character name,
                               handle    h,
                               boolean   transactionDistinct,
                               Object... args)
       {
          return new character(invoke(name, h, false, false, false, transactionDistinct, null, args));
       }

       /**
        * Invoke a remote external procedure or internal procedure. The validation of the legacy name
        * and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invoke(character name,
                               handle    h,
                               boolean   transactionDistinct,
                               String    modes,
                               Object... args)
       {
          return new character(invoke(name, h, false, false, false, transactionDistinct, modes, args));
       }

       /**
        * Invoke a remote external procedure, internal procedure or user-defined function. The
        * validation for the program name and arguments will be done on the remote side.
        *
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure or function.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure/function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invoke(character name,
                                  handle    h,
                                  boolean   function,
                                  boolean   dynamicFunction,
                                  boolean   superCall,
                                  boolean   transactionDistinct,
                                  String    modes,
                                  Object... args)
       {
          return invoke(0, null, name, h,
                        function, dynamicFunction, superCall, transactionDistinct,
                        modes, args);
       }

       /**
        * Invoke a remote external procedure or internal procedure. The validation of the legacy name
        * and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invoke(long      timeout,
                               character name,
                               handle    h,
                               boolean   transactionDistinct,
                               Object... args)
       {
          return new character(invoke(timeout, name, h,
                                      false, false, false,
                                      transactionDistinct, null, args));
       }

       /**
        * Invoke a remote external procedure or internal procedure. The validation of the legacy name
        * and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public character invoke(long      timeout,
                               character name,
                               handle    h,
                               boolean   transactionDistinct,
                               String    modes,
                               Object... args)
       {
          return new character(invoke(timeout, name, h,
                                      false, false, false,
                                      transactionDistinct, modes, args));
       }

       /**
        * Invoke a remote external procedure, internal procedure or user-defined function. The
        * validation for the program name and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure or function.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure/function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       public BaseDataType invoke(long      timeout,
                                  character name,
                                  handle    h,
                                  boolean   function,
                                  boolean   dynamicFunction,
                                  boolean   superCall,
                                  boolean   transactionDistinct,
                                  String    modes,
                                  Object... args)
       {
          return invoke(timeout, null, name, h,
                        function, dynamicFunction, superCall, transactionDistinct,
                        modes, args);
       }

       /**
        * Handle a generic web service request.  This exposes the Jetty request and response objects
        * to the Agent which will process it, so it is required for the appserver to be local.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    handler
        *           The {@link com.goldencode.p2j.oo.web.WebHandler} implementation.
        * @param    basepath
        *           The service basepath.
        * @param    paths
        *           The split paths for this handler.
        * @param    token
        *           When not null, it represents the token of a FWD context created for an authenticated and
        *           authorized web service call.  The API call will be performed in that context.
        * @param    target
        *           The request original target.
        * @param    request
        *           The Jetty request.
        * @param    response
        *           The Jetty response.
        *
        * @return   A {@link AppServerInvocationResult} instance with any error throw during the web service
        *           processing.
        *
        * @throws   IllegalStateException
        *           If this appserver is not {@link #local}.
        */
       public AppServerInvocationResult handleWebService(long                timeout,
                                                         Class<?>            handler,
                                                         String              basepath,
                                                         String[]            paths,
                                                         String              token,
                                                         String              target,
                                                         HttpServletRequest  request,
                                                         HttpServletResponse response)
       {
          if (!local)
          {
             throw new IllegalStateException("Web services must be handled only by local appservers!");
          }

          int reqId = appServer.uniqueId();
          return appServer.handleWebService(connectionID, reqId, timeout, handler,
                                            basepath, paths, token, target, request, response);
       }

       /**
        * Perform a pseudo-dynamic call where the target procedure doesn't receive the parameter as arguments at
        * the Java method definition, but instead they are managed via {@link LegacyOpenClientCaller} APIs.
        * <p>
        * This allows the target program to act as a controller, where it can prepare the arguments, perform
        * security checks, etc, before dispatching the call to the real target, which can be resolved from the
        * arguments or in some other way.
        *
        * @param    procedure
        *           The target external program.  Must have no parameters defined.
        * @param    argTypes
        *           The argument types, one of the e.g. {@link LegacyJavaAppserverApi#integer()} values.
        * @param    modes
        *           The argument modes, one of the (I)NPUT, (O)UTPUT or INP(U)T-OUTPUT values.
        * @param    args
        *           The actual arguments.
        *
        * @return   A {@link AppServerInvocationResult} instance with all updated OUTPUT/INPUT-OUTPUT arguments
        *           and any returned value.
        */
       public AppServerInvocationResult invokeWithArgs(String    procedure,
                                                       int[]     argTypes,
                                                       String    modes,
                                                       Object... args)
       {
          return invokeWithArgs(0, procedure, argTypes, modes, args);
       }

       /**
        * Perform a pseudo-dynamic call where the target procedure doesn't receive the parameter as arguments at
        * the Java method definition, but instead they are managed via {@link LegacyOpenClientCaller} APIs.
        * <p>
        * This allows the target program to act as a controller, where it can prepare the arguments, perform
        * security checks, etc, before dispatching the call to the real target, which can be resolved from the
        * arguments or in some other way.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    procedure
        *           The target external program.  Must have no parameters defined.
        * @param    argTypes
        *           The argument types, one of the e.g. {@link LegacyJavaAppserverApi#integer()} values.
        * @param    modes
        *           The argument modes, one of the (I)NPUT, (O)UTPUT or INP(U)T-OUTPUT values.
        * @param    args
        *           The actual arguments.
        *
        * @return   A {@link AppServerInvocationResult} instance with all updated OUTPUT/INPUT-OUTPUT arguments
        *           and any returned value.
        */
       public AppServerInvocationResult invokeWithArgs(long      timeout,
                                                       String    procedure,
                                                       int[]     argTypes,
                                                       String    modes,
                                                       Object... args)
       {
          checkConnected();

          if (argTypes.length != args.length)
          {
             throw new IllegalStateException("The length for argument types and the arguments must match!");
          }
          int modeLength = (modes == null ? 0 : modes.length());
          if (modeLength != args.length)
          {
             throw new IllegalStateException("The length for argument modes and the arguments must match!");
          }

          // preprocess the parameters and save them in a distinct array, to be sent to the remote side
          Object[] preprocessed =  preProcessParameters(procedure, modes, args);

          // obtain an unique ID for this request
          int reqId = appServer.uniqueId();
          AppServerInvocationResult result = appServer.invokeWithArgs(connectionID,
                                                                      reqId,
                                                                      timeout,
                                                                      procedure,
                                                                      argTypes,
                                                                      modes,
                                                                      preprocessed);
          checkResult(procedure, args, result);
          return result;
       }

       /**
        * Get the error returned by CONNECT call.
        *
        * @return   See above.
        */
       public NumberedException getConnectError()
       {
          return this.connectError;
       }

       /**
        * Get the CONNECT call result.
        *
        * @return   See above.
        */
       public character getConnectResult()
       {
          return new character(this.connectResult);
       }

       /**
        * Get the name of the connected appserver.
        *
        * @return   See above.
        */
       @Override
       public String getServerName()
       {
          return this.appServerName;
       }

       /**
        * Get the name of the connected appserver.
        *
        * @return   See above.
        *
        * @deprecated  Use {@link #getServerName()} instead.
        */
       @Deprecated
       public String getAppServerName()
       {
          return this.appServerName;
       }

       /**
        * Initialize the specified proxy procedure.
        *
        * @param    asyncReq
        *           The async request which refers an uninitialized proxy procedure
        * @param    proxy
        *           The proxy procedure wrapper.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       void initializeProxy(AsyncRequestImpl      asyncReq,
                            ProxyProcedureWrapper proxy,
                            boolean               transactionDistinct,
                            String                modes,
                            Object[]              args)
       {
          checkConnected();
          String code = proxy.getResourceId();
          int agentId = proxy.getAgentId();

          Object[] preprocessed = preProcessParameters(code, modes, args);

          // obtain an unique ID for this request
          int reqId = appServer.uniqueId();
          asyncReq.setRequestId(reqId);

          String connectionID = asyncReq.getConnectionId() != null ?
             asyncReq.getConnectionId() :
             this.connectionID;

          if (!asyncReq.isAsync())
          {
             AppServerInvocationResult result = appServer.initializeProxy(connectionID, reqId, code, agentId,
                                                                          transactionDistinct,
                                                                          modes, preprocessed);
             asyncReq.setResult(result);
             proxy.setInitialized(true);
          }
          else
          {
             asyncReq.setAsyncAction(() ->
             {
                AppServerInvocationResult result = appServer.initializeProxy(connectionID, reqId, code, agentId,
                                                                             transactionDistinct,
                                                                             modes, preprocessed);
                asyncReq.setResult(result);
                proxy.setInitialized(true);
             });
          }
       }

       /**
        * Obtain a proxy for the given external procedure. This will also reserve an agent which will
        * be used for all subsequent invocation (including external procedure initialization).
        *
        * @param    name
        *           A name of a remote external procedure.
        *
        * @return   A result containing the details about this proxy procedure (resource ID, agent ID).
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       AppServerInvocationResult obtainProxy(String name)
       {
          checkConnected();

          return appServer.obtainProxy(connectionID, name);
       }

       /**
        * Invoke a remote external procedure, internal procedure or user-defined function. The
        * validation for the program name and arguments will be done on the remote side.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    name
        *           The legacy 4GL name for the procedure/function.
        * @param    h
        *           The proxy procedure in which to invoke the internal procedure or function.
        *           If <code>null</code>, an external procedure is invoked.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May be <code>null</code>
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure/function invocation.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       BaseDataType invoke(long             timeout,
                           AsyncRequestImpl asyncReq,
                           character        name,
                           handle           h,
                           boolean          function,
                           boolean          dynamicFunction,
                           boolean          superCall,
                           boolean          transactionDistinct,
                           String           modes,
                           Object...        args)
       {
          checkConnected();

          if (asyncReq != null && asyncReq._isCancelled())
          {
             // if the procedure is cancelled, nothing else to do
             asyncReq.setResult(new AppServerInvocationResult());
             return new unknown();
          }

          String pCode;
          int agentId;
          if (h != null)
          {
             if (!ProcedureManager.isProxy(h))
             {
                throw new IllegalArgumentException("The passed handle is not a proxy procedure!");
             }

             Object o = h.getResource();
             if (o instanceof ProxyProcedureWrapper && !proxyProcedureInitialized((ProxyProcedureWrapper) o))
             {
                return null;
             }

             ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) o;
             pCode = proxy.getResourceId();
             agentId = proxy.getAgentId();
          }
          else
          {
             pCode = null;
             agentId = -1;
          }

          // preprocess the parameters and save them in a distinct array, to be sent to the remote
          // side
          Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);

          // obtain an unique ID for this request
          int reqId = appServer.uniqueId();
          if (asyncReq != null)
          {
             asyncReq.setRequestId(reqId);
          }

          if (asyncReq == null || !asyncReq.isAsync())
          {
             AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout,
                                                                 name, pCode, agentId,
                                                                 function,dynamicFunction, superCall,
                                                                 transactionDistinct,
                                                                 modes, preprocessed);

             checkResult(name.toStringMessage(), args, result);

             return (BaseDataType) result.getResult();
          }
          else
          {
             // if this is an async request, then no error messages are shown and errors are recorded
             // before the EVENT-PROCEDURE is executed
             asyncReq.setAsyncAction(() ->
             {
                AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout,
                                                                    name, pCode, agentId,
                                                                    function,dynamicFunction, superCall,
                                                                    transactionDistinct,
                                                                    modes, preprocessed);

                asyncReq.setResult(result);
             });

             return null;
          }
       }

       /**
        * Invoke a remote external procedure, internal procedure, user-defined function or web service
        * operation. The validation for the name and arguments will be specific to the established
        * server connection subtype.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    asyncReq
        *           When not-null, an {@link AsyncRequestImpl} instance for the associated async
        *           request.
        * @param    cfg
        *           The invoke configuration.
        *
        * @return   The result of this procedure/function invocation or <code>null</code> if the
        *           invocation could not be performed.
        */
       @Override
       public BaseDataType invoke(long timeout, AsyncRequestImpl asyncReq, InvokeConfig cfg)
       {
          checkConnected();

          if (asyncReq != null && asyncReq._isCancelled())
          {
             // if the procedure is cancelled, nothing else to do
             asyncReq.setResult(new AppServerInvocationResult());
             return new unknown();
          }

          handle h = cfg.getInHandle();
          if (h != null)
          {
             Object o = h.getResource();
             if (!(ProcedureManager.isProxy(h) || (o instanceof DeferredProgramWrapper)))
             {
                throw new IllegalArgumentException("The passed handle is not a proxy procedure!");
             }

             if (o instanceof ProxyProcedureWrapper && !proxyProcedureInitialized((ProxyProcedureWrapper) o))
             {
                return null;
             }
          }

          // preprocess the parameters and save them in a distinct array, to be sent to the remote
          // side
          Object[] args = cfg.getArguments() == null ? new Object[0] : cfg.getArguments();
          Object[] preprocessed =  preProcessParameters(cfg.getTarget(), cfg.getModes(), args);

          // obtain an unique ID for this request
          int reqId = appServer.uniqueId();
          if (asyncReq != null)
          {
             asyncReq.setRequestId(reqId);
          }

          cfg.setArguments(preprocessed);

          if (asyncReq == null || !asyncReq.isAsync())
          {
             // do not transfer this to the remote side, use a different instance
             h = cfg.getProcedureHandle();

             if (h != null)
             {
                cfg.setProcedureHandle(new handle());
             }

             AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout, cfg);
             checkResult(cfg.getTarget(), args, result);

             if (result.isInvalidInHandle())
             {
                // invalidate this handle and delete it.
                ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) cfg.getInHandle().getResource();
                proxy.set(null);
                proxy.delete();
             }

             if (h != null)
             {
                String procId = result.getProcedureId();
                int agentId = result.getAgentId();
                character fileName = getProcedureName(procId, agentId);
                ProxyProcedureWrapper p = new ProxyProcedureWrapper(procId, fileName, agentId);
                p.setInitialized(true);
                h.assign(p);

                cfg.setProcedureHandle(h);
             }

             return (BaseDataType) result.getResult();
          }
          else
          {
             // if this is an async request, then no error messages are shown and errors are recorded
             // before the EVENT-PROCEDURE is executed
             asyncReq.setAsyncAction(() ->
             {
                AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout, cfg);
                asyncReq.setResult(result);
             });

             return null;
          }
       }

       /**
        * Invoke the given Java method directly.
        * <p>
        * All arguments are sent by references: if it was marked as an OUTPUT or INPUT-OUTPUT, any change to that
        * instance will be reflected back to the caller.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    cfg
        *           The configuration with the Java method to be invoked.
        *
        * @return   The result of this method invocation, or <code>null</code> if this is a void method.
        *
        * @throws   JavaInvokeException
        *           In case any exception was thrown by the method.
        */
       public Object invoke(int timeout, JavaInvokeConfig cfg)
       throws JavaInvokeException
       {
          checkConnected();

          // for Java API calls, the arguments are sent always 'as is' - there is no special preprocessing for
          // them.  if they are OUTPUT, they must be mutable, and postProcessResult will receive them in the
          // expected state

          int reqId = appServer.uniqueId();

          AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout, cfg);
          if (result.getError() != null)
          {
             throw new JavaInvokeException("Error invoking Java service " + cfg.getTarget(), result.getError());
          }

          // no post-process required

          return result.getResult();
       }

       /**
        * Obtain a proxy for the given external procedure.
        *
        * @param    name
        *           A name of a remote external procedure.
        *
        * @return   A proxy to this procedure. May be invalid if the remote side can't resolve the
        *           procedure.
        */
       ProxyProcedureWrapper obtainProxy(character name)
       {
          AppServerInvocationResult res = name.isUnknown() ? null : obtainProxy(name.toStringMessage());

          if (res.isInvalidInHandle())
          {
             return new ProxyProcedureWrapper(null, name, -1);
          }
          else
          {
             ProxyProcedureWrapper proxy = new ProxyProcedureWrapper(res.getProcedureId(),
                                                                     name,
                                                                     res.getAgentId(),
                                                                     res.getChildConnectionID());
             proxy.setServer(this.server);
             return proxy;
          }
       }

       /**
        * Using the details from the last appserver call, restore the SERVER:RESPONSE-INFO with the
        * remote side's SESSION:CURRENT-RESPONSE-INFO, as received in the result.
        *
        * @param    result
        *           The invocation result.  If <code>null</code>, use {@link #lastResult}.
        * @param    responseInfo
        *           The SERVER:RESPONSE-INFO where to restore the details.
        */
       void restoreResponse(AppServerInvocationResult result,
                            object<? extends OerequestInfo> responseInfo)
       {
          if (result == null)
          {
             result = lastResult;
             lastResult = null;
          }

          responseInfo.assign(ObjectOps.newInstance(OerequestInfo.class));

          OerequestInfo ref = responseInfo.ref();
          ref.initialize(null, // the response doesn't have the procedure name...
                         result.getClientContextId(),
                         result.getRequestId(),
                         result.getVersionInfo(),
                         result.getClientType(),
                         result.getAgentId(),
                         result.getSessionId(),
                         result.getThreadId(),
                         result.getClientPrincipal());
       }

       /**
        * Write the specified arguments to file.
        *
        * @param    target
        *           The target of this remote call.
        * @param   request
        *          Flag indicating this is for a request.
        * @param   args
        *          The arguments to write.
        */
       private static void writeArguments(String target, boolean request, Object[] args)
       {
          String fname = (request ? "request" : "response") + "-" + COUNTER.incrementAndGet() + ".txt";
          try
          {
             BufferedWriter bw = new BufferedWriter(new FileWriter(fname));
             bw.write(target);
             bw.write("\n");
             for (int i = 0; i < args.length; i++)
             {
                Object arg = args[i];
                String s = arg == null ? "null"
                                       : (arg instanceof BaseDataType
                                            ? ((BaseDataType) arg).toStringMessage()
                                            : arg.toString());

                bw.write("Argument " + i + ":\n");
                bw.write(s);
                bw.write('\n');
             }
             bw.close();
          }
          catch (Exception e)
          {
             LOG.severe("", e);
          }
       }

       /**
        * Check the parameters before a remote call.  All OUTPUT or INPUT-OUTPUT parameters need to be
        * a P2J data wrapper, a {@link DataModelObject DMO}, {@link TableParameter} or a
        * {@link TableResultSet result set}.
        * <p>
        * This will not change the references in the passed argument list. Instead, it will return a
        * distinct array which will need to be sent to the remote side.
        *
        * @param    target
        *           The target of this remote call.
        * @param    modes
        *           The parameter modes.  If <code>null</code>, no validation is performed.
        * @param    args
        *           The argument list.
        *
        * @return   The preprocessed parameters.
        */
       private Object[] preProcessParameters(String target, String modes, Object... args)
       {
          if (local)
          {
             Object[] params = new Object[args.length];
             System.arraycopy(args, 0, params, 0, args.length);

             for (int i = 0; i < params.length; i++)
             {
                Object arg = params[i];
                char mode = (modes == null ? 0 : modes.charAt(i));

                if (arg instanceof TableResultSet || arg instanceof TableWrapper)
                {
                   // emulate as if it was sent over the network
                   TableWrapper wrapper = arg instanceof TableWrapper
                                             ? (TableWrapper) arg
                                             : new TableWrapper((TableResultSet) arg);

                   // make sure the remote side receives a known class
                   TableWrapper param = new TableWrapper();

                   try
                   {
                      ByteArrayOutputStream baos = new ByteArrayOutputStream();
                      ObjectOutputStream out = new ObjectOutputStream(baos);
                      wrapper.writeExternal(out);
                      out.close();

                      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                      ObjectInputStream in = new ObjectInputStream(bais);
                      param.readExternal(in);
                      in.close();
                   }
                   catch (Throwable t)
                   {
                      throw new RuntimeException(t);
                   }

                   params[i] = param;
                }
                else if (arg instanceof DataSetContainer || arg instanceof DatasetWrapper)
                {
                   // emulate as if it was sent over the network
                   DatasetWrapper wrapper = arg instanceof DatasetWrapper
                                              ? (DatasetWrapper) arg
                                              : new DatasetWrapper((DataSetContainer) arg);
                   // make sure the remote side receives a known class
                   DatasetWrapper param = new DatasetWrapper();

                   try
                   {
                      ByteArrayOutputStream baos = new ByteArrayOutputStream();
                      ObjectOutputStream out = new ObjectOutputStream(baos);
                      wrapper.writeExternal(out);
                      out.close();

                      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                      ObjectInputStream in = new ObjectInputStream(bais);
                      param.readExternal(in);
                      in.close();
                   }
                   catch (Throwable t)
                   {
                      throw new RuntimeException(t);
                   }

                   params[i] = param;
                }
                else if (arg instanceof TableParameter)
                {
                   if (modes == null)
                   {
                      throw new IllegalArgumentException("Table parameters must have their mode set!");
                   }

                   TableParameter tableParam = (TableParameter) arg;
                   if (tableParam.isValid())
                   {
                      Temporary tempDMO = tableParam.getTable();
                      boolean append = tableParam.isAppend();

                      boolean input = (mode == 'I' || mode == 'U');
                      boolean output = (mode == 'O' || mode == 'U');

                      int schemaMarshalLevel = tableParam.getSchemaMarshalLevel();

                      arg = new TableWrapper(new TempTableResultSet(tempDMO, input, output, append, input));
                      String tableName = ((BufferImpl) tempDMO).buffer().getDmoInfo().legacyTable;
                      ((TableWrapper) arg).setTableName(tableName);
                      ((TableWrapper) arg).setSchemaMarshalLevel(schemaMarshalLevel);
                      handle tableHandle = ((Buffer) tempDMO).tableHandle();
                      if (tableHandle._isValid())
                      {
                         ((TableWrapper) arg).init((TempTable) tableHandle.getResource());
                      }

                      try
                      {
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
                         ObjectOutputStream out = new ObjectOutputStream(baos);
                         ((TableWrapper) arg).writeExternal(out);
                         out.close();

                         arg = new TableWrapper();
                         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                         ObjectInputStream in = new ObjectInputStream(bais);
                         ((TableWrapper) arg).readExternal(in);
                         in.close();
                      }
                      catch (Throwable t)
                      {
                         throw new RuntimeException(t);
                      }
                   }
                   else
                   {
                      TableWrapper tableWrapper = new TableWrapper();
                      tableWrapper.setTableHandle(true); // TABLE parameter cannot be invalid
                      arg = tableWrapper;
                   }

                   params[i] = arg;
                }
                else if (arg instanceof DataSetParameter)
                {
                   if (modes == null)
                   {
                      throw new IllegalArgumentException("Dataset parameters must have their mode set!");
                   }

                   DataSetParameter dsParam = (DataSetParameter) arg;
                   DataSet ds = dsParam.getDataset();
                   boolean append = dsParam.isAppend();

                   boolean input  = (mode == 'I' || mode == 'U');
                   boolean output = (mode == 'O' || mode == 'U');

                   arg = new DatasetWrapper(new DataSetContainer(ds, input, output, append, input));
                   if (input && ds != null)
                   {
                      ((DatasetWrapper) arg).setDatasetName(ds.name().toStringMessage());
                   }

                   try
                   {
                      ByteArrayOutputStream baos = new ByteArrayOutputStream();
                      ObjectOutputStream out = new ObjectOutputStream(baos);
                      ((DatasetWrapper) arg).writeExternal(out);
                      out.close();

                      arg = new DatasetWrapper();
                      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                      ObjectInputStream in = new ObjectInputStream(bais);
                      ((DatasetWrapper) arg).readExternal(in);
                      in.close();
                   }
                   catch (Throwable t)
                   {
                      throw new RuntimeException(t);
                   }

                   params[i] = arg;
                }
                else if (params[i] instanceof memptr)
                {
                   params[i] = new MemoryBuffer(((memptr) params[i]).asByteArray());
                }
                else if (params[i] instanceof object)
                {
                   params[i] = new LegacyObject((object) params[i], true);
                }

                // TODO: arrays
             }

             if (DEBUG)
             {
                writeArguments(target, true, params);
             }

             return params;
          }

          // the preprocessed parameter list
          Object[] params = new Object[args.length];

          final String msg =
             "Parameter %s in mode %s needs to be a FWD data type compatible for transport over appserver.";

          for (int i = 0; i < args.length; i++)
          {
             Object arg = args[i];

             char mode = (modes == null ? 0 : modes.charAt(i));

             if (arg instanceof TableParameter)
             {
                if (modes == null)
                {
                   throw new IllegalArgumentException("Table parameters must have their mode set!");
                }

                TableParameter tableParam = (TableParameter) arg;
                Temporary tempDMO = tableParam.getTable();
                boolean append = tableParam.isAppend();

                boolean input  = (mode == 'I' || mode == 'U');
                boolean output = (mode == 'O' || mode == 'U');

    //            try
    //            {
                   arg = new TableWrapper(new TempTableResultSet(input ? tempDMO : null,
                                                                 input,
                                                                 output,
                                                                 append));
                   if (input)
                   {
                      if (tempDMO != null && ((Buffer) tempDMO).tableHandle()._isValid())
                      {
                         ((TableWrapper) arg).init((TempTable) ((BufferImpl) tempDMO).tableHandleNative());
                      }
                      String tableName = ((BufferImpl) tempDMO).buffer().getDmoInfo().legacyTable;
                      ((TableWrapper) arg).setTableName(tableName);
                   }
    //            }
    //            catch (ClassNotFoundException e)
    //            {
    //               String err = String.format("Could not process temp DMO %s as a parameter!",
    //                                           tempDMO.getClass().getName());
    //
    //               throw new IllegalArgumentException(err, e);
    //            }
             }
             else if (arg instanceof DataSetParameter)
             {
                if (modes == null)
                {
                   throw new IllegalArgumentException("Dataset parameters must have their mode set!");
                }

                DataSetParameter dsParam = (DataSetParameter) arg;
                DataSet ds = dsParam.getDataset();
                boolean append = dsParam.isAppend();

                boolean input  = (mode == 'I' || mode == 'U');
                boolean output = (mode == 'O' || mode == 'U');

                arg = new DatasetWrapper(new DataSetContainer(ds, input, output, append));
                if (input && ds != null)
                {
                   ((DatasetWrapper) arg).setDatasetName(ds.name().toStringMessage());
                }
             }
             else if (arg instanceof DataSetContainer)
             {
                DataSetContainer ds = (DataSetContainer) arg;
                if (mode == 'O')
                {
                   ds.clearRows();
                }
                arg = new DatasetWrapper(ds);
             }
             else if (arg instanceof DatasetWrapper)
             {
                // just keep it, clearing rows if necessary
                if (mode == 'O')
                {
                   DataSetContainer ds = ((DatasetWrapper) arg).getResource();
                   if (ds != null)
                   {
                      ds.clearRows();
                   }
                }
             }
             else if (arg instanceof TableWrapper)
             {
                // just keep it
             }
             else if (arg instanceof TableResultSet)
             {
                TableResultSet rs = (TableResultSet) arg;
                if (mode == 'O')
                {
                   rs.clearRows();
                }
                // make sure the remote side receives a known class
                arg = new TableWrapper(rs);
             }
             else if (arg instanceof ResultSet)
             {
                // allow only input mode
                if (mode != 'I')
                {
                   throw new IllegalArgumentException(
                      "java.sql.ResultSet instances are allowed only for INPUT table parameters!");
                }

                final String err = "Could not serialize the ResultSet on position " + i + ".";
                try
                {
                   @SuppressWarnings("resource")
                   final ResultSet rs = (ResultSet) arg;
                   final ResultSetMetaData rsmd = rs.getMetaData();
                   final int columns = rsmd.getColumnCount();

                   // build a custom TableResultSet which reads the data and metadata from the
                   // sql-like ResultSet instance, and wraps it in a TableWrapper for transport

                   arg = new TableWrapper(new TableResultSet()
                   {
                      private int nextCol = 0;

                      @Override
                      public boolean hasMoreRows()
                      {
                         try
                         {
                            return rs.next();
                         }
                         catch (SQLException e)
                         {
                            throw new IllegalArgumentException(err, e);
                         }
                      }

                      @Override
                      public Object[] nextRow()
                      {
                         try
                         {
                            Object[] row = new Object[columns];

                            for (int i = 1; i <= columns; i++)
                            {
                               row[i - 1] = rs.getObject(i);
                            }

                            return row;
                         }
                         catch (SQLException e)
                         {
                            throw new IllegalArgumentException(err, e);
                         }
                      }

                      @Override
                      public Object[] nextRowMeta()
                      {
                         // nothing is kept here...
                         return new Object[TempRecord._META_PROPERTIES_LENGTH];
                      }

                      @Override
                      public PropertyDefinition nextProperty()
                      {
                         try
                         {
                            // TODO: extent properties?
                            // TODO: handle uses same SQL type as character
                            // TODO: rowid uses same SQL type as int64
                            // TODO: recid uses same SQL type as integer
                            int type = rsmd.getColumnType(nextCol);
                            return new PropertyDefinition(rsmd.getColumnName(nextCol),
                                                          DataTypeHelper.getBdtClass(type));
                         }
                         catch (SQLException e)
                         {
                            throw new IllegalArgumentException(err, e);
                         }
                      }

                      @Override
                      public boolean hasMoreProperties()
                      {
                         nextCol = nextCol + 1;
                         return nextCol <= columns;
                      }
                   });
                }
                catch (SQLException e)
                {
                   throw new IllegalArgumentException(err, e);
                }
             }
             else if (arg instanceof memptr)
             {
                arg = new MemoryBuffer(((memptr) arg).asByteArray());
             }
             else if (arg instanceof object)
             {
                arg = new LegacyObject((object) arg);
             }
             else
             {
                // check if we have an array
                boolean isArray = arg.getClass().isArray();

                Object[] data = null;
                if (isArray)
                {
                   // make sure we are going over each element in the array
                   int length = Array.getLength(arg);
                   data = (Object[]) Array.newInstance(arg.getClass().getComponentType(), length);
                   for (int j = 0; j < length; j++)
                   {
                      data[j] = Array.get(arg, j);
                   }
                }
                else
                {
                   data = new Object[] { arg };
                }

                for (int j = 0; j < data.length; j++)
                {
                   Object darg = data[j];

                   if (darg instanceof handle)
                   {
                      handle h = (handle) darg;
                      if (h.isUnknown())
                      {
                         // if the handle is unknown, send a null resource ID
                         darg = new RemoteResourceImpl(null);
                      }
                      else
                      {
                         WrappedResource res = h.getResource();
                         if (res instanceof RemoteResource)
                         {
                            darg = res;
                         }
                         else
                         {
                            // any resource can be sent to the remote side, even if the remote side
                            // can't resolve it properly
                            darg = new RemoteResourceImpl(h.toStringMessage());
                         }
                      }
                   }
                   else if (darg instanceof memptr)
                   {
                      // do not allow memptrs to be used
                      throw new IllegalArgumentException(
                        "memptr instances can not be used from Java code.  Use MemoryBuffer instances instead.");
                   }
                   else if (darg instanceof object)
                   {
                      // do not allow memptrs to be used
                      throw new IllegalArgumentException(
                        "object instances can not be used from Java code.  Use LegacyObject instances instead.");
                   }
                   else if ((mode == 'U' || mode == 'O') &&
                            !(darg instanceof BaseDataType ||
                              darg instanceof MemoryBuffer ||
                              darg instanceof LegacyObject))
                   {
                      // for output/input-output modes, darg needs to be one of the known types
                      String err = String.format(msg, darg, (mode == 'U' ? "INPUT-OUTPUT" : "OUTPUT"));
                      throw new IllegalArgumentException(err);
                   }

                   data[j] = darg;
                }

                arg = (isArray ? data : data[0]);
             }

             params[i] = arg;
          }

          if (DEBUG)
          {
             writeArguments(target, true, params);
          }

          return params;
       }

       /**
        * Verify the current invocation result instance, by checking if any errors were thrown by the
        * remote call.
        * <p>
        * Also, this receives an argument list and updates it, depending on the parameter's mode
        * (OUTPUT or INPUT-OUTPUT) and if the remote side sent an updated value of this argument. The
        * post-processing of the argument list is done only if no errors were received from the remote
        * side.
        *
        * @param    target
        *           The target of this remote call.
        * @param    args
        *           The arguments which need to be processed.
        * @param    result
        *           The result which needs to be checked for errors.
        *
        * @throws   ConditionException
        *           If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
        * @throws   RuntimeException
        *           If the API call ended with other type of {@link Exception}, which is not a
        *           {@link ConditionException}.
        */
       private void checkResult(String target, Object[] args, AppServerInvocationResult result)
       {
          // restore the response
          if (server != null)
          {
             restoreResponse(result, server.getResponseInfo());
          }

          Throwable t = result.getError();
          if (t != null)
          {
             if (t instanceof NumberedException)
             {
                if (!SessionManager.get().isLeaf())
                {
                   // do this only if we are in a P2J code
                   NumberedException ne = (NumberedException) t;
                   if (server != null)
                   {
                      ErrorManager.displayError(ne.getNumber(), ne.getMessage(), false);
                   }
                   else
                   {
                      throw new ErrorConditionException(ne.getMessage());
                   }
                }
                else
                {
                   // we are not in P2J code, let the error propagate
                   throw new RuntimeException(t);
                }

                Throwable cause = t.getCause();
                if (cause instanceof StopConditionException)
                {
                   throw (ConditionException) cause;
                }
             }
             else if (t instanceof QuitConditionException)
             {
                // the Agent has disconnected because of a QUIT - you need to reconnect...
                disconnectedImpl();
                throw (QuitConditionException) t;
             }
             else if (t instanceof LegacyErrorException)
             {
                if (disabledLegacyErrors)
                {
                   throw new ErrorConditionException(
                      "ERROR condition: The Server application has returned an error. (7243) (7211)");
                }

                LegacyErrorException le = (LegacyErrorException) t;
                LegacyErrorException newEx = le.duplicate();

                throw newEx;
             }
             else if (t instanceof ConditionException)
             {
                ConditionException conExc = (ConditionException) t;

                if (conExc.getKeyCode() == 14810)
                {
                   // TODO: maybe add more properties in the appserver result to set the other flags
                   ErrorManager.recordOrThrowError(new int[] { conExc.getKeyCode() },
                                                   new String[] { conExc.getMessage() },
                                                   false,
                                                   false,
                                                   false);
                }
                else
                {
                   throw conExc;
                }
             }
             else
             {
                throw new RuntimeException(t);
             }
          }

          // no errors found, post-process results
          postProcessResult(target, args, result);
       }

       /**
        * On the requester side, this receives an argument list and updates it, depending on the
        * parameter's mode (OUTPUT or INPUT-OUTPUT) and if the remote side sent an updated value of
        * this argument.
        *
        * @param    target
        *           The target of this remote call.
        * @param    args
        *           The arguments (from the requester side) which need to be processed.
        * @param    result
        *           The result which needs to be checked for errors.
        */
       private void postProcessResult(String target, Object[] args, AppServerInvocationResult result)
       {
          Object[] remoteArgs = result.getArguments();
          if (remoteArgs == null || remoteArgs.length != args.length)
          {
             // some error happened and the remote side did not invoke anything or no arguments used
             return;
          }

          for (int i = 0; i < args.length; i++)
          {
             if (remoteArgs[i] == null)
             {
                // only output/input-output parameters are sent back
                continue;
             }

             Object localArg = args[i];
             Object remoteArg = remoteArgs[i];
             if (remoteArg instanceof DatasetWrapper)
             {
                DatasetWrapper remoteResult = (DatasetWrapper) remoteArg;

                if (localArg instanceof DataSetContainer || localArg instanceof DatasetWrapper)
                {
                   if (local)
                   {
                      try
                      {
                         // emulate as if it was sent over the network...
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
                         ObjectOutputStream out = new ObjectOutputStream(baos);
                         remoteResult.writeExternal(out);
                         out.close();

                         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                         ObjectInputStream in = new ObjectInputStream(bais);
                         remoteResult = new DatasetWrapper();
                         remoteResult.readExternal(in);
                         in.close();
                      }
                      catch (Throwable t)
                      {
                         throw new RuntimeException(t);
                      }
                   }

                   if (remoteResult.isAsXml())
                   {
                      ((DatasetWrapper) localArg).setXmlDataset(remoteResult.getXmlDataset());
                   }
                   else
                   {
                      DataSetContainer dsParam;
                      if (localArg instanceof DataSetContainer)
                      {
                         dsParam = (DataSetContainer) localArg;
                      }
                      else
                      {
                         dsParam = ((DatasetWrapper) localArg).getResource();
                      }

                      dsParam.setValid(remoteResult.isValid());
                      dsParam.setTableDefinitions(remoteResult.getTableDefs());
                      dsParam.setRelationDefinitions(remoteResult.getRelationsDefs());
                      dsParam.setStructureName(remoteResult.getDatasetName());
                   }
                }
                else
                {
                   DataSetParameter dsParam = (DataSetParameter) localArg;
                   boolean append = dsParam.isAppend();

                   if (local)
                   {
                      try
                      {
                         // emulate as if it was sent over the network...
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
                         ObjectOutputStream out = new ObjectOutputStream(baos);
                         remoteResult.writeExternal(out);
                         out.close();

                         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                         ObjectInputStream in = new ObjectInputStream(bais);
                         remoteResult = new DatasetWrapper();
                         remoteResult.readExternal(in);
                         in.close();
                      }
                      catch (Throwable t)
                      {
                         throw new RuntimeException(t);
                      }
                   }

                   if (remoteResult.getTableDefs() == null)
                   {
                      // remote result is unknown
                      if (dsParam.getOriginalDataSet() != null)
                      {
                         // can't 'unknown' a static dataset
                      }
                      else
                      {
                         dsParam.getDatasetHandle().setUnknown();
                      }
                   }
                   else
                   {
                      DataSet ds = dsParam.getDataset();
                      if (ds == null)
                      {
                         dsParam.createDataSet(remoteResult);
                      }
                      else
                      {
                         // copy individual tables
                         int bufCnt = ds.numBuffers().intValue();
                         for (int k = 0; k < bufCnt; k++)
                         {
                            Buffer dstBuf = ds.getBufferByIndex(k + 1);
                            Temporary dstDMO = TemporaryBuffer.getDefaultBuffer(dstBuf.tableHandle());
                            TableWrapper src = remoteResult.getTableWrapper(k);

                            TemporaryBuffer.insertAllRows(src,
                                           dstDMO,
                                           append,
                                           TemporaryBuffer.CopyTableMode.OUTPUT_TABLE_HANDLE_PARAM_MODE,
                                           dsParam.getParameterIndex());
                         }
                      }
                   }
                }
             }
             else if (remoteArg instanceof TableWrapper)
             {
                // here there are two cases:
                // 1. from 4GL code, when a temp DMO is available
                // 2. from Java code, when a temp DMO is not available

                TableWrapper remoteResult = (TableWrapper) remoteArg;
                if (localArg instanceof TableParameter)
                {
                   if (local)
                   {
                      try
                      {
                         // emulate as if it was sent over the network...
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
                         ObjectOutputStream out = new ObjectOutputStream(baos);
                         remoteResult.writeExternal(out);
                         out.close();

                         remoteResult = new TableWrapper();
                         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                         ObjectInputStream in = new ObjectInputStream(bais);
                         remoteResult.readExternal(in);
                         in.close();
                      }
                      catch (Throwable t)
                      {
                         throw new RuntimeException(t);
                      }
                   }

                   // DMO on the requester side, use the P2J persistence layer to update the  data
                   TableParameter tableParam = (TableParameter) localArg;
                   Temporary tempDMO = tableParam.getTable();
                   boolean append = tableParam.isAppend();

                   int tableParameterIndex = i + 1;
                   if (tempDMO != null)
                   {
                      TemporaryBuffer.insertAllRows(remoteResult,
                                         tempDMO,
                                         append,
                                         TemporaryBuffer.CopyTableMode.OUTPUT_TABLE_HANDLE_PARAM_MODE,
                                         tableParameterIndex);
                   }
                   else if (remoteResult.getTableName() != null)
                   {
                      TempTableBuilder builder = new TempTableBuilder();
                      builder.createFromTableWrapper(remoteResult, tableParameterIndex);
                      tableParam.getTableHandle().assign(builder);
                   }
                }
                else if (localArg instanceof TableResultSet || localArg instanceof TableWrapper)
                {
                   if (local)
                   {
                      try
                      {
                         // emulate as if it was sent over the network...
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
                         ObjectOutputStream out = new ObjectOutputStream(baos);
                         remoteResult.writeExternal(out);
                         out.close();

                         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                         ObjectInputStream in = new ObjectInputStream(bais);
                         if (localArg instanceof TableWrapper)
                         {
                            remoteResult = (TableWrapper) localArg;
                         }
                         else
                         {
                            remoteResult = new TableWrapper();
                         }
                         remoteResult.readExternal(in);
                         in.close();
                      }
                      catch (Throwable t)
                      {
                         throw new RuntimeException(t);
                      }
                   }

                   if (remoteResult.isAsXml())
                   {
                      ((TableWrapper) localArg).setXmlTable(remoteResult.getXmlTable());
                   }
                   else if (localArg instanceof TableResultSet)
                   {
                      // we don't have a DMO, let the requester have access to the result set
                      // the remoteArg reference must be passed to the local arg
                      TableResultSet localResult = (TableResultSet) localArg;
                      localResult.setProperties(remoteResult.getProperties());
                      localResult.setRows(remoteResult.getRows());
                      localResult.setStructureName(remoteResult.getTableName());
                   }
                   else
                   {
                      TableWrapper localResult = (TableWrapper) localArg;
                      localResult.setResultSet(null); // this must be cleared
                      localResult.setPeerDef(remoteResult.getPeerDef());
                      localResult.setProperties(remoteResult.getProperties());
                      localResult.setRows(remoteResult.getRows());
                      localResult.setRowsMeta(remoteResult.getRowsMeta());
                      localResult.setTableName(remoteResult.getTableName());
                      localResult.setIndexes(remoteResult.getIndexes());
                      localResult.setNumIndexes(remoteResult.getNumIndexes());
                      localResult.setXmlns(remoteResult.getXmlns());
                      localResult.setXmlPrefix(remoteResult.getXmlPrefix());
                   }
                }
             }
             else
             {
                boolean isArray = localArg.getClass().isArray();
                Object[] remoteData = null;
                Object[] localData = null;

                if (isArray)
                {
                   // check if this argument is an array
                   if (remoteArg instanceof AbstractExtentParameter)
                   {
                      int len = ((AbstractExtentParameter) remoteArg).getVariable().length;
                      remoteData = new Object[len];
                   }
                   else
                   {
                      remoteData = new Object[Array.getLength(remoteArg)];
                   }
                   localData = new Object[Array.getLength(localArg)];

                   for (int j = 0; j < remoteData.length; j++)
                   {
                      if (remoteArg instanceof AbstractExtentParameter)
                      {
                         remoteData[j] = ((AbstractExtentParameter) remoteArg).getVariable()[j];
                      }
                      else
                      {
                         remoteData[j] = Array.get(remoteArg, j);
                      }
                   }
                   for (int j = 0; j < localData.length; j++)
                   {
                      localData[j] = Array.get(localArg, j);
                   }

                   if (localData.length == 0)
                   {
                      // must be dynamic
                      localData = new Object[remoteData.length];
                      Class<?> ctype = localArg.getClass().getComponentType();
                      BaseDataType bdt = BaseDataType.class.isAssignableFrom(ctype)
                                            ? BaseDataType.generateUnknown(ctype)
                                            : null;
                      for (int j = 0; j < localData.length; j++)
                      {
                         if (localArg.getClass().getComponentType() == MemoryBuffer.class)
                         {
                            localData[j] = new MemoryBuffer();
                         }
                         else if (localArg.getClass().getComponentType() == LegacyObject.class)
                         {
                            localData[j] = new LegacyObject(new object(_BaseObject_.class));
                         }
                         else
                         {
                            localData[j] = bdt.duplicate();
                         }
                      }
                      args[i] = localData;
                   }
                }
                else
                {
                   remoteData = new Object[] { remoteArg };
                   localData = new Object[] { localArg };
                }

                if (remoteData.length != localData.length)
                {
                   final String msg =
                      "The remote side sent an array of length %d while the local argument is %d " +
                      "in length, for argument on position %d.";
                   throw new IllegalArgumentException(String.format(msg,
                                                                    remoteData.length,
                                                                    localData.length,
                                                                    i));
                }

                for (int j = 0; j < remoteData.length; j++)
                {
                   Object dremoteArg = remoteData[j];
                   Object dlocalArg = localData[j];

                   if (dremoteArg instanceof BaseDataType)
                   {
                      if (dlocalArg instanceof BaseDataType)
                      {
                         ((BaseDataType) dlocalArg).assign((BaseDataType) dremoteArg);
                      }
                      else
                      {
                         // requester's arg should have been a BDT, throw an error here but this
                         // should never happen as preProcessParameters takes care to enforce this
                         // constraint
                         final String msg =
                           "Argument on index %d in OUTPUT or INPUT-OUTPUT mode must be of a P2J " +
                           "wrapper type.";
                         throw new IllegalArgumentException(String.format(msg, i));
                      }
                   }
                   else if (dlocalArg instanceof handle)
                   {
                      handle h = (handle) dlocalArg;
                      RemoteResource res = (RemoteResource) dremoteArg;
                      if (res.unknown())
                      {
                         h.setUnknown();
                      }
                      else
                      {
                         h.assign(res);
                      }
                   }
                   else if (dlocalArg instanceof MemoryBuffer)
                   {
                      MemoryBuffer remoteBuffer = (MemoryBuffer) dremoteArg;
                      MemoryBuffer localBuffer  = (MemoryBuffer) dlocalArg;

                      localBuffer.setValue(remoteBuffer.getValue());
                   }
                   else if (dlocalArg instanceof memptr)
                   {
                      MemoryBuffer remoteBuffer = (MemoryBuffer) dremoteArg;
                      memptr localBuffer  = (memptr) dlocalArg;

                      localBuffer.assign(remoteBuffer.getValue());
                   }
                   else if (dlocalArg instanceof LegacyObject)
                   {
                      LegacyObject remoteObject = (LegacyObject) dremoteArg;
                      LegacyObject localObject = (LegacyObject) dlocalArg;
                      localObject.assign(remoteObject);
                   }
                   else if (dlocalArg instanceof object)
                   {
                      LegacyObject remoteObject = (LegacyObject) dremoteArg;
                      object localObject = (object) dlocalArg;
                      _BaseObject_ ref = remoteObject.restore(local);
                      localObject.assign(ref);
                   }
                }
             }
          }

          if (DEBUG)
          {
             writeArguments(target, false, args);
          }

          // check if the result is a resource
          if (result.getResult() instanceof RemoteResource)
          {
             RemoteResource res = (RemoteResource) result.getResult();
             handle h = new handle();
             if (res.unknown())
             {
                h.setUnknown();
             }
             else
             {
                h.assign(res);
             }
             result.setResult(h);
          }
       }

       /**
        * Set the CONNECT call result.
        *
        * @param    result
        *           The result return by the latest CONNECT call.
        */
       private void setConnectResult(character result)
       {
          this.connectResult = result.toStringMessage();
       }

       /**
        * Set the error returned by the CONNECT call.
        *
        * @param    error
        *           The error returned by the CONNECT call.
        */
       private void setConnectError(NumberedException error)
       {
          this.connectError = error;
       }

       /**
        * Check if the appserver is connected and if not, throw a {@link IllegalStateException}.
        *
        * Details about the server-side problem are found in the {@link #connectError} and in the
        * {@link #connectResult} fields.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       private void checkConnected()
       {
          if (connectError != null)
          {
             String msg = "The appserver is not connected - check the connection result!";
             throw new IllegalStateException(msg, connectError);
          }
          if (connectionID == null)
          {
             throw new IllegalStateException("The connection to the appserver was not established!");
          }
          if (!isConnected())
          {
             throw new IllegalStateException("The connection to the appserver was lost.");
          }
       }

       /**
        * Wait for the initialization of a proxy procedure.
        *
        * @param    p
        *           The proxy procedure.
        */
       private boolean proxyProcedureInitialized(ProxyProcedureWrapper p)
       {
          p.waitForInitialize();

          if (!p.isInitialized())
          {
             // TODO: the proxy program has not been initialized - raise exception?
             return false;
          }

          return true;
       }
    }