InvokeConfig.java

/*
** Module   : InvokeConfig.java
** Abstract : Defines a builder configuration with the details required to execute a RUN statement. 
**
** Copyright (c) 2018-2025, Golden Code Development Corporation.

**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA  20181029 Created initial version.
** 002 CA  20190628 Refactored to allow OO invocations and to be used for appserver calls.  Moved invoke() API 
**                  to ControlFlowOps.
** 003 CA  20200427 Added support for Agent's SESSION:CURRENT-REQUEST-INFO, as sent by the remote
**                  SERVER:REQUEST-INFO.
**     CA  20200514 Added getter and setter for requestID.
** 004 IAS 20200908 Rework (de)serialization.
** 005 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.
**     CA  20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
**                  allow caching of the resolved target.
**     CA  20221010 runSuper() and execute() calls always mark the configuration as FUNCTION.
**     OM  20221017 Implemented toString() method for debugging usage.
** 006 CA  20220428 Fixed (de)serialization.
** 007 HC  20230116 Replaced some handle usages with the actual wrapped resources for
**                  performance.
** 008 CA  20230215 Use PayloadSerializer for serialization.
** 009 CA  20231020 Fixed (de)serialization for inHandle referencing a ProxyProcedureWrapper.
** 010 GBB 20250403 restoreRequest made public to be accessed by CoreAppserver.
*/

/*
** 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 static com.goldencode.util.NativeTypeSerializer.*; 
import java.io.*;
import java.util.*;

import com.goldencode.p2j.oo.lang.*;
import com.goldencode.util.*;

/**
 * Defines a configuration which can be used by a RUN or dynamic function call.  All the 
 * information required to perform a call can be setup using a builder approach, as all setters
 * return this instance.
 * <p>
 * The final call can be delegated to {@link ControlFlowOps#invoke(InvokeConfig)}. 
 */
public class InvokeConfig
implements Externalizable
{
   /** Constant to identify field {@link #inHandle} during serialization. */
   private static final int FIELD_IN_HANDLE = 0;

   /** Constant to identify field {@link #function} during serialization. */
   private static final int FIELD_FUNCTION = FIELD_IN_HANDLE + 1;

   /** Constant to identify field {@link #persistent} during serialization. */
   private static final int FIELD_PERSISTENT = FIELD_FUNCTION + 1;

   /** Constant to identify field {@link #singleRun} during serialization. */
   private static final int FIELD_SINGLE_RUN = FIELD_PERSISTENT + 1;

   /** Constant to identify field {@link #singleton} during serialization. */
   private static final int FIELD_SINGLETON = FIELD_SINGLE_RUN + 1;

   /** Constant to identify field {@link #procedureHandle} during serialization. */
   private static final int FIELD_PROC_HANDLE = FIELD_SINGLETON + 1;

   /** Constant to identify field {@link #serverHandle} during serialization. */
   private static final int FIELD_SERVER_HANDLE = FIELD_PROC_HANDLE + 1;

   /** Constant to identify field {@link #transactionDistinct} during serialization. */
   private static final int FIELD_TX_DISTINCT = FIELD_SERVER_HANDLE + 1;

   /** Constant to identify field {@link #asynchronous} during serialization. */
   private static final int FIELD_ASYNC = FIELD_TX_DISTINCT + 1;

   /** Constant to identify field {@link #asyncHandle} during serialization. */
   private static final int FIELD_ASYNC_HANDLE = FIELD_ASYNC + 1;

   /** Constant to identify field {@link #eventProcedure} during serialization. */
   private static final int FIELD_EVT_PROC = FIELD_ASYNC_HANDLE + 1;

   /** Constant to identify field {@link #eventProcedureContext} during serialization. */
   private static final int FIELD_EVT_PROC_CONTEXT = FIELD_EVT_PROC + 1;

   /** Constant to identify field {@link #arguments} during serialization. */
   private static final int FIELD_ARGS = FIELD_EVT_PROC_CONTEXT + 1;

   /** Constant to identify field {@link #modes} during serialization. */
   private static final int FIELD_MODES = FIELD_ARGS + 1;

   /** Constant to identify field {@link #portHandle} during serialization. */
   private static final int FIELD_PORT_HANDLE = FIELD_MODES + 1;

   /** Constant to identify field {@link #dynamicFunction} during serialization. */
   private static final int FIELD_DYN_FUNC = FIELD_PORT_HANDLE + 1;

   /** Constant to identify field {@link #superCall} during serialization. */
   private static final int FIELD_SUPER_CALL = FIELD_DYN_FUNC + 1;

   /** Constant to identify field {@link #isClass} during serialization. */
   private static final int FIELD_IS_CLASS = FIELD_SUPER_CALL + 1;

   /** Constant to identify field {@link #returnValue} during serialization. */
   private static final int FIELD_RETURN_VALUE = FIELD_IS_CLASS + 1;
   
   /** Constant to identify field {@link #clientContextId} during serialization. */
   private static final int FIELD_CLIENTCONTEXTID = FIELD_RETURN_VALUE + 1;
   
   /** Constant to identify field {@link #requestId} during serialization. */
   private static final int FIELD_REQUESTID = FIELD_CLIENTCONTEXTID + 1;
   
   /** Constant to identify field {@link #clientPrincipal} during serialization. */
   private static final int FIELD_CLIENTPRINCIPAL = FIELD_REQUESTID + 1;
   
   /** Constant to identify field {@link #versionInfo} during serialization. */
   private static final int FIELD_VERSIONINFO = FIELD_CLIENTPRINCIPAL + 1;
   
   /** Constant to identify field {@link #clientType} during serialization. */
   private static final int FIELD_CLIENTTYPE = FIELD_VERSIONINFO + 1;

   /** Constant to identify field {@link #webServiceToken} during serialization. */
   private static final int FIELD_WEB_SERVICE_TOKEN = FIELD_CLIENTTYPE + 1;

   /** Constant to identify field {@link #asThread} during serialization. */
   private static final int FIELD_AS_THREAD = FIELD_WEB_SERVICE_TOKEN + 1;

   /** This call's target (a function, internal procedure or program name). */
   private String target = null;
   
   /** Flag indicating that this is a legacy OO class. */
   private boolean isClass = false;
   
   /**
    * When specified, it will mean the target will be a function or internal procedure, using
    * the given procedure handle as a start.
    */
   private handle inHandle = null;
   
   /** Flag indicating if this is a function or procedure call. */
   private boolean function = false;

   /** When set, this call will be an external program call, ran persistent. */
   private boolean persistent = false;
   
   /** When set, this call will be an external program call, ran in SINGLE-RUN mode. */
   private boolean singleRun = false;
   
   /** When set, this call will be an external program call, ran in SINGLETON mode. */
   private boolean singleton = false;
   
   /** The handle where to pass back the reference to the newly persistent-run external program. */
   private handle procedureHandle = null;
   
   /** The handle where to pass back the reference in case of a web service invocation. */
   private handle portHandle = null;
   
   /** The remote server handle where to run this request. */
   private handle serverHandle = null;
   
   /** Flag indicating if TRANSACTION-DISTINCT option is present. */
   private boolean transactionDistinct = false;
   
   /** Flag indicating if ASYNCHRONOUS option is present. */
   private boolean asynchronous = false;
   
   /** Flag indicating if AS-THREAD FWD-extension option is present. */
   private boolean asThread = false;
   
   /** The handle where to pass back the reference to the {@link AsyncRequest async resource}. */
   private handle asyncHandle = null;
   
   /**
    * This represents the name of the internal procedure for the EVENT-PROCEDURE clause, null if 
    * this clause is not specified.
    */
   private character eventProcedure = null;
   
   /**
    * The handle to a procedure in which context the specified EVENT-PROCEDURE internal procedure 
    * is found, null if EVENT-PROCEDURE clause is not specified.
    */
   private handle eventProcedureContext = null;
   
   /** The arguments for this call. */
   private Object[] arguments = new Object[0];
   
   /** A string representation of the modes of each parameter. */
   private String modes = null;
   
   /** Flag indicating if this is a DYNAMIC-FUNCTION call. */
   private boolean dynamicFunction = false;
   
   /** Flag indicating if this is a RUN SUPER or SUPER(). call. */
   private boolean superCall = false;
   
   /** Flag indicating if the OO method call is function-like (return value is needed). */
   private boolean returnValue = false;

   /** The value of the <code>OERequestInfo:</code> field. */
   private String clientContextId = null;
   
   /** The value of the <code>OERequestInfo:requestId</code> field. */
   private String requestId = null;
   
   /** The value of the <code>OERequestInfo:clientPrincipal</code> field. */
   private byte[] clientPrincipal = null;
   
   /** The value of the <code>OERequestInfo:versionInfo</code> field. */
   private String[] versionInfo = null;
   
   /** The value of the <code>OERequestInfo:clientType</code> field. */
   private String clientType = null;
   
   /** A token sent via a web service request, to execute the target service in the specified FWD context. */
   private String webServiceToken = null;

   /** The callsite instance (at the converted code) for this call. */
   private InvokeConfig callSite = null;
   
   /**
    * Default constructor.
    */
   public InvokeConfig()
   {
      // no-op
   }
   
   /**
    * Copy constructor.  This also saves the call-site reference, to be able to cache the target.
    */
   public InvokeConfig(InvokeConfig callSite)
   {
      this.callSite = callSite;
      
      target = callSite.target;
      isClass = callSite.isClass;
      inHandle = callSite.inHandle == null ? null : new handle(callSite.inHandle);
      function = callSite.function;
      persistent = callSite.persistent;
      singleRun = callSite.singleRun;
      singleton = callSite.singleton;
      procedureHandle = callSite.procedureHandle == null ? null : new handle(callSite.procedureHandle);
      portHandle = callSite.portHandle == null ? null : new handle(callSite.portHandle);
      serverHandle = callSite.serverHandle == null ? null : new handle(callSite.serverHandle);
      transactionDistinct = callSite.transactionDistinct;
      asynchronous = callSite.asynchronous;
      asThread = callSite.asThread;
      asyncHandle = callSite.asyncHandle == null ? null : new handle(callSite.asyncHandle);
      eventProcedure = callSite.eventProcedure == null ? null : new character(callSite.eventProcedure);
      eventProcedureContext = callSite.eventProcedureContext == null 
                                 ? null 
                                 : new handle(callSite.eventProcedureContext);
      if (callSite.arguments != null && callSite.arguments.length > 0)
      {
         arguments = Arrays.copyOf(callSite.arguments, callSite.arguments.length);
      }
      modes = callSite.modes;
      dynamicFunction = callSite.dynamicFunction;
      superCall = callSite.superCall;
      returnValue = callSite.returnValue;
      clientContextId = callSite.clientContextId;
      requestId = callSite.requestId;
      if (callSite.clientPrincipal != null)
      {
         clientPrincipal = Arrays.copyOf(callSite.clientPrincipal, callSite.clientPrincipal.length);
      }
      if (callSite.versionInfo != null)
      {
         versionInfo = Arrays.copyOf(callSite.versionInfo, callSite.versionInfo.length);
      }
      clientType = callSite.clientType;
      webServiceToken = callSite.webServiceToken;
   }
   
   /**
    * Create a new invocation for the given target.
    *  
    * @param    target
    *           The target to be invoked.
    */
   public InvokeConfig(character target)
   {
      this.target = target.getValue();
   }
   
   /**
    * Create a new invocation for the given target.
    *  
    * @param    target
    *           The target to be invoked.
    */
   public InvokeConfig(String target)
   {
      this.target = target;
   }
   
   /**
    * Clone this instance.
    * 
    * @return   A copy of this instance.
    */
   @Override
   public InvokeConfig clone()
   {
      return new InvokeConfig(this);
   }
   
   /**
    * Execute this config for a callback procedure associated with a resource event.
    * 
    * @param    resource
    *           The resource associated with the {@link #callSite call-site}.
    *           
    * @return   The result of the invocation, if this is a function call.
    */
   public BaseDataType executeForResource(WrappedResource resource)
   {
      return ControlFlowOps.invoke(null, resource, this);
   }
   
   /**
    * Execute this config as a <code>RUN SUPER</code> statement with the provided arguments.
    * 
    * @param    args
    *           The arguments.
    */
   public void runSuper(Object... args)
   {
      setArguments(args);
      setSuperCall(true);
      
      ControlFlowOps.invoke(null, null, this);
   }
   
   /**
    * Execute this config as a <code>SUPER()</code> function, with the provided arguments and return type.
    * 
    * @param    type
    *           The return type.
    * @param    args
    *           The arguments.
    *           
    * @return   The function's return value.
    */
   public <T extends BaseDataType> T runSuper(Class<T> type, Object... args)
   {
      setArguments(args);
      setSuperCall(true);
      setFunction(true);
      
      BaseDataType superVal = ControlFlowOps.invoke(type, null, this);
      
      T ret = null;
      if (type.isAssignableFrom(superVal.getClass()))
      {
         ret = (T) superVal;
      }
      else
      {
         ret = (T) BaseDataType.generateUnknown(type);
         ret.assign(superVal);
      }
      
      return ret;
   }
   
   /**
    * Execute this config for a RUN call.
    */
   public void run(Object... args)
   {
      setArguments(args);
      
      ControlFlowOps.invoke(null, null, this);
   }
   
   /**
    * Execute this config for a function typed call.
    * 
    * @return   The function's returned value.
    */
   public <T extends BaseDataType> T execute(Class<T> cls, Object... args)
   {
      setArguments(args);
      setFunction(true);
      
      return (T) ControlFlowOps.invoke(cls, null, this);
   }
   
   /**
    * Execute this config for a function call.
    * 
    * @return   The function's returned value.
    */
   public BaseDataType execute(Object... args)
   {
      setArguments(args);
      setFunction(true);
      
      return ControlFlowOps.invoke(null, null, this);
   }
   
   /**
    * Get the {@link #callSite}.
    * 
    * @return   See above.
    */
   public InvokeConfig getCallSite()
   {
      return callSite;
   }
   
   /**
    * Set this invocation's {@link #target}.
    * 
    * @param    target
    *           The target to be invoked.
    *           
    * @return   This instance.
    */
   public InvokeConfig setTarget(character target)
   {
      this.target = target.getValue();
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #target}.
    * 
    * @param    target
    *           The target to be invoked.
    *           
    * @return   This instance.
    */
   public InvokeConfig setTarget(String target)
   {
      this.target = target;
      
      return this;
   }
   
   /**
    * Mark this configuration as a OO invocation.
    *           
    * @return   This instance.
    */
   public InvokeConfig setClass()
   {
      this.isClass = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #returnValue} flag.
    * 
    * @param    returnValue
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setReturnValue(boolean returnValue)
   {
      this.returnValue = returnValue;
      
      return this;
   }

   /**
    * Set this invocation's {@link #function} flag.
    * 
    * @param    function
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setFunction(boolean function)
   {
      this.function = function;
      
      return this;
   }

   /**
    * Set this invocation's {@link #function} flag to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig function()
   {
      this.function = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #persistent} flag.
    * 
    * @param    persistent
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setPersistent(boolean persistent)
   {
      this.persistent = persistent;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #persistent} flag to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig persistent()
   {
      this.persistent = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #singleRun} flag.
    * 
    * @param    singleRun
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setSingleRun(boolean singleRun)
   {
      this.singleRun = singleRun;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #singleRun} flag to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig singleRun()
   {
      this.singleRun = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #singleton} flag.
    * 
    * @param    singleton
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setSingleton(boolean singleton)
   {
      this.singleton = singleton;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #singleton} flag to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig singleton()
   {
      this.singleton = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #procedureHandle} handle where to save the persistent program.
    * 
    * @param    procedureHandle
    *           A handle variable or field reference.
    *           
    * @return   This instance.
    */
   public InvokeConfig setProcedureHandle(handle procedureHandle)
   {
      // the reference is saved
      this.procedureHandle = procedureHandle;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #portHandle} handle where to save the web service.
    * 
    * @param    portHandle
    *           A handle variable or field reference.
    *           
    * @return   This instance.
    */
   public InvokeConfig setPortHandle(handle portHandle)
   {
      // the reference is saved
      this.portHandle = portHandle;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #serverHandle} remote handle where to perform the call.
    * 
    * @param    serverHandle
    *           A handle to a remote appserver.
    *           
    * @return   This instance.
    */
   public InvokeConfig setServerHandle(handle serverHandle)
   {
      this.serverHandle = serverHandle == null ? null : new handle(serverHandle);
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #transactionDistinct} flag.
    * 
    * @param    transactionDistinct
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setTransactionDistinct(boolean transactionDistinct)
   {
      this.transactionDistinct = transactionDistinct;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #transactionDistinct} flag to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig transactionDistinct()
   {
      this.transactionDistinct = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #asynchronous} flag.
    * 
    * @param    asynchronous
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setAsynchronous(boolean asynchronous)
   {
      this.asynchronous = asynchronous;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #asThread} flag to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig asThread()
   {
      this.asThread = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #asThread} flag.
    * 
    * @param    asThread
    *           The new value of this flag.
    *           
    * @return   This instance.
    */
   public InvokeConfig setAsThread(boolean asThread)
   {
      this.asThread = asThread;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #asynchronous} flag to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig asynchronous()
   {
      this.asynchronous = true;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #asyncHandle} handle where to save the async resource.
    * 
    * @param    asyncHandle
    *           A handle variable or field reference.
    *           
    * @return   This instance.
    */
   public InvokeConfig setAsyncHandle(handle asyncHandle)
   {
      // the reference is saved
      this.asyncHandle = asyncHandle;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #eventProcedure EVENT-PROCEDURE} option.
    * 
    * @param    eventProcedure
    *           The event procedure name.
    *           
    * @return   This instance.
    */
   public InvokeConfig setEventProcedure(character eventProcedure)
   {
      this.eventProcedure = new character(eventProcedure);
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #eventProcedure EVENT-PROCEDURE} option.
    * 
    * @param    eventProcedure
    *           The event procedure name.
    *           
    * @return   This instance.
    */
   public InvokeConfig setEventProcedure(String eventProcedure)
   {
      this.eventProcedure = new character(eventProcedure);
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #eventProcedureContext EVENT-PROCEDURE-CONTEXT} option.
    * 
    * @param    eventProcedureContext
    *           The event procedure context.
    *           
    * @return   This instance.
    */
   public InvokeConfig setEventProcedureContext(handle eventProcedureContext)
   {
      this.eventProcedureContext = new handle(eventProcedureContext);

      return this;
   }
   
   /**
    * Set this invocation's {@link #inHandle IN handle} option.
    * 
    * @param    inHandle
    *           A program handle where to perform the call.
    *           
    * @return   This instance.
    */
   public InvokeConfig setInHandle(handle inHandle)
   {
      this.inHandle = inHandle == null ? null : new handle(inHandle);
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #arguments}.
    * 
    * @param    arguments
    *           A invocation's arguments.
    *           
    * @return   This instance.
    */
   public InvokeConfig setArguments(Object... arguments)
   {
      this.arguments = arguments;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #modes argument modes}.
    * 
    * @param    modes
    *           A invocation's argument modes.
    *           
    * @return   This instance.
    */
   public InvokeConfig setModes(String modes)
   {
      this.modes = modes;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #superCall} flag.
    * 
    * @param    superCall
    *           Flag indicating that this is a super-call.
    *           
    * @return   This instance.
    */
   public InvokeConfig setSuperCall(boolean superCall)
   {
      this.superCall = superCall;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #dynamicFunction} flag.
    * 
    * @param    dynamicFunction
    *           Flag indicating that this is a dynamic-function call.
    *           
    * @return   This instance.
    */
   public InvokeConfig setDynamicFunction(boolean dynamicFunction)
   {
      this.dynamicFunction = dynamicFunction;
      
      return this;
   }
   
   /**
    * Set this invocation's {@link #dynamicFunction} and {@link #function} flags to <code>true</code>.
    * 
    * @return   This instance.
    */
   public InvokeConfig dynamicFunction()
   {
      this.function = true;
      this.dynamicFunction = true;
      
      return this;
   }

   /**
    * Set the {@link #requestId}.
    * 
    * @param    requestId
    *           The request ID.
    *           
    * @return   This instance.
    */
   public InvokeConfig setRequestId(String requestId)
   {
      this.requestId = requestId;
      
      return this;
   }

   /**
    * Set the {@link #webServiceToken}.
    * 
    * @param    webServiceToken
    *           The web service token.
    *           
    * @return   This instance.
    */
   public InvokeConfig setWebServiceToken(String webServiceToken)
   {
      this.webServiceToken = webServiceToken;
      
      return this;
   }
   
   /**
    * Get the {@link #isClass} flag.
    * 
    * @return   See above.
    */
   public boolean isClass()
   {
      return isClass;
   }
   
   /**
    * Get the {@link #returnValue} flag.
    * 
    * @return   See above.
    */
   public boolean isReturnValue()
   {
      return returnValue;
   }
   
   /**
    * Get the {@link #function} flag.
    * 
    * @return   See above.
    */
   public boolean isFunction()
   {
      return function;
   }

   /**
    * Get the {@link #persistent} flag.
    * 
    * @return   See above.
    */
   public boolean isPersistent()
   {
      return persistent;
   }

   /**
    * Get the {@link #singleRun} flag.
    * 
    * @return   See above.
    */
   public boolean isSingleRun()
   {
      return singleRun;
   }

   /**
    * Get the {@link #singleton} flag.
    * 
    * @return   See above.
    */
   public boolean isSingleton()
   {
      return singleton;
   }

   /**
    * Get the {@link #transactionDistinct} flag.
    * 
    * @return   See above.
    */
   public boolean isTransactionDistinct()
   {
      return transactionDistinct;
   }

   /**
    * Get the {@link #asynchronous} flag.
    * 
    * @return   See above.
    */
   public boolean isAsynchronous()
   {
      return asynchronous;
   }

   /**
    * Get the {@link #asThread} flag.
    * 
    * @return   See above.
    */
   public boolean isAsThread()
   {
      return asThread;
   }

   /**
    * Get the caller's target.
    * 
    * @return   the {@link #target}.
    */
   public String getTarget()
   {
      return target;
   }

   /**
    * Get the target handle for internal entry call.
    * 
    * @return   the {@link #inHandle}
    */
   public handle getInHandle()
   {
      return inHandle;
   }

   /**
    * Get the {@link #procedureHandle}.
    * 
    * @return   the {@link #procedureHandle}
    */
   public handle getProcedureHandle()
   {
      return procedureHandle;
   }

   /**
    * Get the {@link #portHandle}.
    * 
    * @return   the {@link #portHandle}
    */
   public handle getPortHandle()
   {
      return portHandle;
   }

   /**
    * Get the {@link #serverHandle}.
    * 
    * @return   the {@link #serverHandle}
    */
   public handle getServerHandle()
   {
      return serverHandle;
   }

   /**
    * Get the caller's {@link #asyncHandle}.
    * 
    * @return    the {@link #asyncHandle}.
    */
   public handle getAsyncHandle()
   {
      return asyncHandle;
   }

   /**
    * Get the caller's {@link #eventProcedure}.
    * 
    * @return   the {@link #eventProcedure}
    */
   public character getEventProcedure()
   {
      return eventProcedure;
   }

   /**
    * Get the caller's {@link #eventProcedureContext}.
    * 
    * @return   the {@link #eventProcedureContext}
    */
   public handle getEventProcedureContext()
   {
      return eventProcedureContext;
   }

   /**
    * Get the caller's {@link #arguments}.
    * 
    * @return   the {@link #arguments}
    */
   public Object[] getArguments()
   {
      return arguments;
   }

   /**
    * Get the caller's {@link #modes}.
    * 
    * @return   the {@link #modes}
    */
   public String getModes()
   {
      return modes;
   }

   /**
    * Get the caller's {@link #superCall}.
    * 
    * @return   the {@link #superCall}
    */
   public boolean isSuperCall()
   {
      return superCall;
   }
   
   /**
    * Get the caller's {@link #dynamicFunction}.
    * 
    * @return   the {@link #dynamicFunction}
    */
   public boolean isDynamicFunction()
   {
      return dynamicFunction;
   }
   
   /**
    * Get the request ID.
    * 
    * @return   The {@link #requestId}.
    */
   public String getRequestId()
   {
      return requestId;
   }
   
   /**
    * Get the web service token.
    * 
    * @return   The {@link #webServiceToken}.
    */
   public String getWebServiceToken()
   {
      return webServiceToken;
   }
   
   /**
    * Serialize this invoke configuration.  To reduce the size of the written data, 
    * {@link #computeWriting()} will be used to set a bit for each field in this class, which was
    * changed from its default value.  Only the fields which are no longer holding the default
    * value will be serialized.
    * <p>
    * The {@link #target} field is always written.
    * 
    * @param    out
    *           The output stream.
    */
   @Override
   public void writeExternal(ObjectOutput out)
   throws IOException
   {
      // compute what to write
      long writing = computeWriting();
      out.writeLong(writing);

      out.writeUTF(target);
      
      if (singleRun)
      {
         out.writeBoolean(singleRun);
      }
      if (singleton)
      {
         out.writeBoolean(singleton);
      }
      if (inHandle != null)
      {
         if (singleRun || singleton)
         {
            out.writeUTF(((DeferredProgram) inHandle.get()).getName());
         }
         else
         {
            WrappedResource res = inHandle.getResource();
            boolean isProxy = res instanceof ProxyProcedureWrapper;
            out.writeBoolean(isProxy);
            if (isProxy)
            {
               ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) res;
               out.writeUTF(proxy.getFileName().getValue());
               out.writeInt(proxy.getAgentId());
            }
            out.writeUTF((String) inHandle.get());
         }
      }
      if (function)
      {
         out.writeBoolean(function);
      }
      if (persistent)
      {
         out.writeBoolean(persistent);
      }
      if (procedureHandle != null)
      {
         procedureHandle.writeExternal(out);
      }
      if (serverHandle != null)
      {
         serverHandle.writeExternal(out);
      }
      if (transactionDistinct)
      {
         out.writeBoolean(transactionDistinct);
      }
      if (asynchronous)
      {
         out.writeBoolean(asynchronous);
      }
      if (asyncHandle != null)
      {
         asyncHandle.writeExternal(out);
      }
      if (eventProcedure != null)
      {
         eventProcedure.writeExternal(out);
      }
      if (eventProcedureContext != null)
      {
         eventProcedureContext.writeExternal(out);
      }
      if (arguments != null && arguments.length > 0)
      {
         PayloadSerializer.writePayload(out, arguments);
      }
      if (modes != null && !modes.isEmpty())
      {
         out.writeUTF(modes);
      }
      if (portHandle != null)
      {
         portHandle.writeExternal(out);
      }
      if (dynamicFunction)
      {
         out.writeBoolean(dynamicFunction);
      }
      if (superCall)
      {
         out.writeBoolean(superCall);
      }
      if (isClass)
      {
         out.writeBoolean(isClass);
      }
      if (returnValue)
      {
         out.writeBoolean(returnValue);
      }
      
      if (clientContextId != null)
      {
         out.writeUTF(clientContextId);
      }
      if (requestId != null)
      {
         out.writeUTF(requestId);
      }
      if (clientPrincipal != null)
      {
         writeByteArray(out, clientPrincipal);
      }
      if (versionInfo != null)
      {
         writeStringArray(out, versionInfo);
      }
      if (clientType != null)
      {
         out.writeUTF(clientType);
      }
      if (webServiceToken != null)
      {
         out.writeUTF(webServiceToken);
      }
      if (asThread)
      {
         out.writeBoolean(asThread);
      }
   }

   /**
    * Read this invoke configuration from a stream.  Only the fields which are no longer holding 
    * the default value will be read (see {@link #computeWriting()}).
    * <p>
    * The {@link #target} field is always read.
    * 
    * @param    in
    *           The input stream.
    */
   @Override
   public void readExternal(ObjectInput in)
   throws IOException,
          ClassNotFoundException
   {
      long writing = in.readLong();
      target = in.readUTF();
      
      if ((writing & (1l << FIELD_SINGLE_RUN)) != 0)
      {
         singleRun = in.readBoolean();
      }
      if ((writing & (1l << FIELD_SINGLETON)) != 0)
      {
         singleton = in.readBoolean();
      }
      if ((writing & (1l << FIELD_IN_HANDLE)) != 0)
      {
         inHandle = new handle();
         if (singleRun || singleton)
         {
            String code = in.readUTF();
            inHandle.assign(new DeferredProgramWrapper(new DeferredProgram(code)));
         }
         else
         {
            boolean isProxy = in.readBoolean();
            if (isProxy)
            {
               String filename = in.readUTF();
               int agentId = in.readInt();
               String referent = in.readUTF();
               ProxyProcedureWrapper proxy = new ProxyProcedureWrapper(referent, new character(filename), agentId);
               inHandle.assign(proxy);
            }
            else
            {
               String code = in.readUTF();
               inHandle.assign(handle.fromString(code));
            }
         }
      }
      if ((writing & (1l << FIELD_FUNCTION)) != 0)
      {
         function = in.readBoolean();
      }
      if ((writing & (1l << FIELD_PERSISTENT)) != 0)
      {
         persistent = in.readBoolean();
      }
      if ((writing & (1l << FIELD_PROC_HANDLE)) != 0)
      {
         procedureHandle = new handle();
         procedureHandle.readExternal(in);
      }
      if ((writing & (1l << FIELD_SERVER_HANDLE)) != 0)
      {
         serverHandle = new handle();
         serverHandle.readExternal(in);
      }
      if ((writing & (1l << FIELD_TX_DISTINCT)) != 0)
      {
         transactionDistinct = in.readBoolean();
      }
      if ((writing & (1l << FIELD_ASYNC)) != 0)
      {
         asynchronous = in.readBoolean();
      }
      if ((writing & (1l << FIELD_ASYNC_HANDLE)) != 0)
      {
         asyncHandle = new handle();
         asyncHandle.readExternal(in);
      }
      if ((writing & (1l << FIELD_EVT_PROC)) != 0)
      {
         eventProcedure = new character();
         eventProcedure.readExternal(in);
      }
      if ((writing & (1l << FIELD_EVT_PROC_CONTEXT)) != 0)
      {
         eventProcedureContext = new handle();
         eventProcedureContext.readExternal(in);
      }
      if ((writing & (1l << FIELD_ARGS)) != 0)
      {
         arguments = (Object[]) PayloadSerializer.readPayload(in);
      }
      else
      {
         arguments = new Object[0];
      }
      if ((writing & (1l << FIELD_MODES)) != 0)
      {
         modes = in.readUTF();
      }
      if ((writing & (1l << FIELD_PORT_HANDLE)) != 0)
      {
         portHandle = new handle();
         portHandle.readExternal(in);
      }
      if ((writing & (1l << FIELD_DYN_FUNC)) != 0)
      {
         dynamicFunction = in.readBoolean();
      }
      if ((writing & (1l << FIELD_SUPER_CALL)) != 0)
      {
         superCall = in.readBoolean();
      }
      if ((writing & (1l << FIELD_IS_CLASS)) != 0)
      {
         isClass = in.readBoolean();
      }
      if ((writing & (1l << FIELD_RETURN_VALUE)) != 0)
      {
         returnValue = in.readBoolean();
      }
      
      if ((writing & (1l << FIELD_CLIENTCONTEXTID)) != 0)
      {
         clientContextId = in.readUTF();
      }
      if ((writing & (1l << FIELD_REQUESTID)) != 0)
      {
         requestId = in.readUTF();
      }
      if ((writing & (1l << FIELD_CLIENTPRINCIPAL)) != 0)
      {
         clientPrincipal = readByteArray(in);
      }
      if ((writing & (1l << FIELD_VERSIONINFO)) != 0)
      {
         versionInfo = readStringArray(in);
      }
      if ((writing & (1l << FIELD_CLIENTTYPE)) != 0)
      {
         clientType = in.readUTF();
      }
      if ((writing & (1l << FIELD_WEB_SERVICE_TOKEN)) != 0)
      {
         webServiceToken = in.readUTF();
      }
      if ((writing & (1l << FIELD_AS_THREAD)) != 0)
      {
         asThread = in.readBoolean();
      }
   }
   
   /**
    * Store the passed request info object, to be sent to a remote side.
    * 
    * @param    requestInfo
    *           The {@link OerequestInfo} instance.
    *           
    * @return   This instance.
    */
   InvokeConfig storeRequest(object<? extends OerequestInfo> requestInfo)
   {
      if (!requestInfo._isValid())
      {
         return this;
      }
      
      OerequestInfo req = requestInfo.ref();
      
      this.clientContextId = req.getClientContextId().getValue();
      this.requestId = AppServerManager.nextContextId(false);
      req.setRequestId(requestId);
      this.requestId = req.getRequestId().getValue();
      ClientPrincipal cp = req.getClientPrincipalNative();
      if (cp != null && cp.valid())
      {
         raw r = cp.exportPrincipal();
         this.clientPrincipal = r.getByteArray();
      }
      object<? extends OeversionInfo> oever = req.getVersionInfo();
      if (oever._isValid())
      {
         OeversionInfo ver = oever.ref();
         versionInfo = new String[]
         {
            ver.getOemajorVersion().getValue(),
            ver.getOeminorVersion().getValue(),
            ver.getOemaintVersion().getValue()
         };
         clientType = ver.getOeclientType().getValue();
      }
      
      return this;
   }

   /**
    * Restore the {@link OerequestInfo} from this call details, as received from a remote call.
    * 
    * @param    req
    *           The request info for the Agent's SESSION.
    * @param    agentId
    *           The agent ID, as computed by the Agent.
    * @param    sessionId
    *           The sessionID, as computed by the Agent.
    * @param    threadId
    *           The threadID, as computed by the Agent.
    */
   public void restoreRequest(OerequestInfo req, Integer agentId, Integer sessionId, Integer threadId)
   {
      if (AppServerManager.isMultiSession())
      {
         // TODO: adapter type based on the request type...
         // REST
         // WEB
         // SOAP
         // req.setAdapterType()
      }

      req.initialize(target,
                     clientContextId, 
                     requestId, 
                     versionInfo, 
                     clientType, 
                     agentId, 
                     sessionId, 
                     threadId, 
                     clientPrincipal);
   }
   
   /**
    * For each field, it sets a bit if the field's value is no longer the default value.
    * <p>
    * For {@link Boolean} fields, this means the field is set to <code>true</code>.
    * <p>
    * For {@link Object} fields, this means the field is now not-null.
    * 
    * @return   A 64-bit value with bits set only for changed fields.
    */
   private long computeWriting()
   {
      long writing = 0;
      if (inHandle != null)
      {
         writing |= (1 << FIELD_IN_HANDLE);
      }
      if (function)
      {
         writing |= (1 << FIELD_FUNCTION);
      }
      if (persistent)
      {
         writing |= (1 << FIELD_PERSISTENT);
      }
      if (singleRun)
      {
         writing |= (1 << FIELD_SINGLE_RUN);
      }
      if (singleton)
      {
         writing |= (1 << FIELD_SINGLETON);
      }
      if (procedureHandle != null)
      {
         writing |= (1 << FIELD_PROC_HANDLE);
      }
      if (serverHandle != null)
      {
         writing |= (1 << FIELD_SERVER_HANDLE);
      }
      if (transactionDistinct)
      {
         writing |= (1 << FIELD_TX_DISTINCT);
      }
      if (asynchronous)
      {
         writing |= (1 << FIELD_ASYNC);
      }
      if (asyncHandle != null)
      {
         writing |= (1 << FIELD_ASYNC_HANDLE);
      }
      if (eventProcedure != null)
      {
         writing |= (1 << FIELD_EVT_PROC);
      }
      if (eventProcedureContext != null)
      {
         writing |= (1 << FIELD_EVT_PROC_CONTEXT);
      }
      if (arguments != null && arguments.length > 0)
      {
         writing |= (1 << FIELD_ARGS);
      }
      if (modes != null && !modes.isEmpty())
      {
         writing |= (1 << FIELD_MODES);
      }
      if (portHandle != null)
      {
         writing |= (1 << FIELD_PORT_HANDLE);
      }
      if (dynamicFunction)
      {
         writing |= (1 << FIELD_DYN_FUNC);
      }
      if (superCall)
      {
         writing |= (1 << FIELD_SUPER_CALL);
      }
      if (isClass)
      {
         writing |= (1 << FIELD_IS_CLASS);
      }
      if (returnValue)
      {
         writing |= (1 << FIELD_RETURN_VALUE);
      }
      
      if (clientContextId != null)
      {
         writing |= (1 << FIELD_CLIENTCONTEXTID);
      }
      if (requestId != null)
      {
         writing |= (1 << FIELD_REQUESTID);
      }
      if (clientPrincipal != null)
      {
         writing |= (1 << FIELD_CLIENTPRINCIPAL);
      }
      if (versionInfo != null)
      {
         writing |= (1 << FIELD_VERSIONINFO);
      }
      if (clientType != null)
      {
         writing |= (1 << FIELD_CLIENTTYPE);
      }
      if (webServiceToken != null)
      {
         writing |= (1 << FIELD_WEB_SERVICE_TOKEN);
      }
      if (asThread)
      {
         writing |= (1 << FIELD_AS_THREAD);
      }
      
      return writing;
   }
   
   /**
    * Construct and return a short representation of this object to be used mainly for debugging. In this
    * case, the returned string is composed of name and parameter types of the target routine.
    *
    * @return  name and parameter types (in, out) of the target routine.
    */
   @Override
   public String toString()
   {
      return target + "(" + modes + ")";
   }
}