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