LegacyOpenClientCaller.java

/*
** Module   : LegacyOpenClientCaller.java
** Abstract : Defines APIs to manage the arguments and perform pseudo-dynamic calls, for the case when  
**            OpenClient uses the 'invokeWithArgs' API.
**
** Copyright (c) 2023-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA  20230712 Created initial version.
** 002 CA  20240328 Added APIs to associate a pseudo-dynamic argument as a parameter to a static dataset/table.
**                  Automatically clean up created resources during argument processing, which are still valid.
** 003 CA  20240512 Do not register invalid resources created from the varargs via 'getTableHandle' or 
**                  'getDataSetHandle'.
*/

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

import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.ProcedureManager.ProcedureHelper;

/**
 * Defines APIs to manage the arguments sent by the {@link LegacyJavaAppserverClient#invokeWithArgs} call.
 * <p>
 * The target program acts as a controller, where it identifies the program to dispatch to call from the
 * arguments or other means.  Only the OUTPUT or INPUT-OUTPUT argument can be sent back to the caller, via
 * the {@link #setArgument} and other setter APIs.
 * <p>
 * Arguments need to be retrieved via the getter APIs, and the target controller will need to prepare them,
 * as needed, to be eventually passed to the {@link #invokeExternalProgram}, {@link #invokeMethod} or 
 * {@link #invokeStaticMethod}, to invoke the real target.
 * <p>
 * The getter, setters or otherwise APIs can be called only from the context of the target external program
 * acting as a controller.  Using these APIs from other code will throw a {@link ErrorConditionException}. 
 */
public class LegacyOpenClientCaller
{
   /** Context-local state. */
   private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
   {
      protected WorkArea initialValue()
      {
         return new WorkArea();
      };
   };
   
   /**
    * Associate the given dataset with the dataset-handle parameter at the given index.
    * <p>
    * This will act as if the given dataset reference has been defined as a parameter for the external program.
    * 
    * @param    idx
    *           The 1-based index for the argument.
    * @param    dataset
    *           The handle to a static dataset or a handle where to store the dynamic dataset, otherwise.
    */
   public static void asDataSetParameter(int idx, handle dataset)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa, true);
      
      validateIndex(wa, idx);
      
      idx = idx - 1;
      
      if (wa.argTypes[idx] != LegacyJavaAppserverApi.TYPE_DATASET_HANDLE)
      {
         ErrorManager.recordOrThrowError(-1, 
                                         "Argument at index " + (idx + 1) + " must be a DATASET-HANDLE to " + 
                                         "associate it as parameter.");
         return;
      }
      
      boolean isInput = wa.modes.charAt(idx) == 'I';
      boolean isOutput = wa.modes.charAt(idx) == 'O';
      boolean isInputOutput = wa.modes.charAt(idx) == 'U';
      DataSetParameter dsp = (DataSetParameter) wa.arguments[idx];

      if (dataset.getResource() instanceof DataSet)
      {
         DataSet ds = (DataSet) dataset.getResource();
         if (ds._dynamic())
         {
            ErrorManager.recordOrThrowError(-1, 
                                            "Argument at index " + (idx + 1 ) + " cannot be associated as " +  
                                            "parameter - the given dataset is dynamic.");
            return;
         }
         
         ds = ds.getProxyDataSet();
         DataSet.associate(dsp, ds, isInput || isInputOutput, isOutput || isInputOutput);
         wa.boundArguments[idx] = true;
      }
      else
      {
         DataSet.createDynamicDataSet(dsp, dataset, isInput || isInputOutput, isOutput || isInputOutput);
         wa.boundArguments[idx] = true;
      }
      
   }
   
   /**
    * Associate the given temp-table with the table-handle parameter at the given index.
    * <p>
    * This will act as if the given DMO reference has been defined as a parameter for the external program.
    * 
    * @param    idx
    *           The 1-based index for the argument.
    * @param    table
    *           The handle to a static temp-table or a handle where to store the dynamic temp-table, otherwise.
    */
   public static void asTableParameter(int idx, handle table)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa, true);
      
      validateIndex(wa, idx);
      
      idx = idx - 1;
      
      if (wa.argTypes[idx] != LegacyJavaAppserverApi.TYPE_TABLE_HANDLE)
      {
         ErrorManager.recordOrThrowError(-1, 
                                         "Argument at index " + (idx + 1) + " must be a TABLE-HANDLE to " + 
                                         "associate it as parameter.");
         return;
      }
      
      boolean isInput = wa.modes.charAt(idx) == 'I';
      boolean isOutput = wa.modes.charAt(idx) == 'O';
      boolean isInputOutput = wa.modes.charAt(idx) == 'U';
      TableParameter tp = (TableParameter) wa.arguments[idx];

      if (table.getResource() instanceof TempTable)
      {
         TempTable tt = (TempTable) table.getResource();
         if (tt._dynamic())
         {
            ErrorManager.recordOrThrowError(-1, 
                                            "Argument at index " + (idx + 1 ) + " cannot be associated as " +  
                                            "parameter - the given temp-table is dynamic.");
            return;
         }
         
         TemporaryBuffer.associate(tp, 
                                   (Temporary) tt.defaultBufferHandleNative(), 
                                   isInput || isInputOutput, 
                                   isOutput || isInputOutput);
         wa.boundArguments[idx] = true;
      }
      else
      {
         TemporaryBuffer.createDynamicTable(tp, table, isInput || isInputOutput, isOutput || isInputOutput);
         wa.boundArguments[idx] = true;
      }
   }
   
   /**
    * Get the number of arguments sent by the remote side.
    * 
    * @return   See above.
    */
   public static integer getNumArguments()
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);
      
      return new integer(wa.argTypes.length);
   }

   /**
    * Check if the argument at the specified index was sent as INPUT.
    * 
    * @param    idx
    *           The 1-based index for the argument.
    *           
    * @return   <code>true</code> if the argument's mode is INPUT.
    */
   public static boolean isInput(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);
      
      validateIndex(wa, idx);
      
      char mode = wa.modes.charAt(idx - 1);
      return mode == 'I';
   }

   /**
    * Check if the argument at the specified index was sent as OUTPUT.
    * 
    * @param    idx
    *           The 1-based index for the argument.
    *           
    * @return   <code>true</code> if the argument's mode is OUTPUT.
    */
   public static boolean isOutput(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx);

      char mode = wa.modes.charAt(idx - 1);
      return mode == 'O';
   }
   
   /**
    * Check if the argument at the specified index was sent as INPUT-OUTPUT.
    * 
    * @param    idx
    *           The 1-based index for the argument.
    *           
    * @return   <code>true</code> if the argument's mode is INPUT-OUTPUT.
    */
   public static boolean isInputOutput(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx);

      char mode = wa.modes.charAt(idx - 1);
      return mode == 'U';
   }
   
   /**
    * Get the argument's type at the specified index.
    * 
    * @param    idx
    *           The 1-based index for the argument.
    *           
    * @return   The string representation of the argument's 4GL type.
    */
   public static String getParameterType(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx);
      
      String argTypeErr = null;

      switch (wa.argTypes[idx - 1])
      {
         case LegacyJavaAppserverApi.TYPE_INTEGER:
            return BaseDataType.Type.INTEGER.getTypeName();
         case LegacyJavaAppserverApi.TYPE_INT64:
            return BaseDataType.Type.INT64.getTypeName();
         case LegacyJavaAppserverApi.TYPE_DECIMAL:
            return BaseDataType.Type.DECIMAL.getTypeName();
         case LegacyJavaAppserverApi.TYPE_MEMPTR:
            return BaseDataType.Type.MEMPTR.getTypeName();
         case LegacyJavaAppserverApi.TYPE_CHARACTER:
            return BaseDataType.Type.CHARACTER.getTypeName();
         case LegacyJavaAppserverApi.TYPE_LONGCHAR:
            return BaseDataType.Type.LONGCHAR.getTypeName();
         case LegacyJavaAppserverApi.TYPE_LOGICAL:
            return BaseDataType.Type.LOGICAL.getTypeName();
         case LegacyJavaAppserverApi.TYPE_DATE:
            return BaseDataType.Type.DATE.getTypeName();
         case LegacyJavaAppserverApi.TYPE_DATETIME:
            return BaseDataType.Type.DATETIME.getTypeName();
         case LegacyJavaAppserverApi.TYPE_DATETIMETZ:
            return BaseDataType.Type.DATETIME_TZ.getTypeName();
         case LegacyJavaAppserverApi.TYPE_RECID:
            return BaseDataType.Type.RECID.getTypeName();
         case LegacyJavaAppserverApi.TYPE_RAW:
            return BaseDataType.Type.RAW.getTypeName();
         case LegacyJavaAppserverApi.TYPE_ROWID:
            return BaseDataType.Type.ROWID.getTypeName();
         case LegacyJavaAppserverApi.TYPE_DATASET_HANDLE:
            return BaseDataType.Type.DATASET_HANDLE.getTypeName();
         case LegacyJavaAppserverApi.TYPE_TABLE_HANDLE:
            return BaseDataType.Type.TABLE_HANDLE.getTypeName();

         // unsupported types
         case LegacyJavaAppserverApi.TYPE_BLOB:
            argTypeErr = "BLOB";
            break;
         case LegacyJavaAppserverApi.TYPE_CLOB:
            argTypeErr = "CLOB";
            break;
         case LegacyJavaAppserverApi.TYPE_HANDLE:
            argTypeErr = "HANDLE";
            break;
         case LegacyJavaAppserverApi.TYPE_UNKNOWN:
            argTypeErr = "UNKNOWN";
            break;
         case LegacyJavaAppserverApi.TYPE_DATASET:
            argTypeErr = "DATASET";
            break;
         case LegacyJavaAppserverApi.TYPE_TABLE:
            argTypeErr = "TABLE";
            break;
            
         default:
            ErrorManager.recordOrThrowError(-1, "Argument type " + wa.argTypes[idx - 1] + " is unknown.");
            return null;
      }
      
      ErrorManager.recordOrThrowError(-1, "Argument type " + argTypeErr + " is not supported.");
      
      return null;
   }
   
   /**
    * Invoke the target external program.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    name
    *           The target external program.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static void invokeExternalProgram(String name, character[] ptypes, Object[] args)
   {
      invokeExternalProgram(new character(name), ptypes, args);
   }
   
   /**
    * Invoke the target external program.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    name
    *           The target external program.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static void invokeExternalProgram(character name, character[] ptypes, Object[] args)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      Object[] oargs = resolveArgs(null, ptypes, args, false);
      ControlFlowOps.invokeExternalProcedure(name, false, null, false, null, null, oargs);
   }

   /**
    * Invoke the target static method in the given legacy class.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    clsName
    *           The target legacy class name.
    * @param    modes
    *           The argument modes.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static Object invokeStaticMethod(String      clsName, 
                                           String      name, 
                                           String      modes, 
                                           character[] ptypes, 
                                           Object[]    args)
   {
      return invokeStaticMethod(new character(clsName), new character(name), modes, ptypes, args);
   }

   /**
    * Invoke the target static method in the given legacy class.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    clsName
    *           The target legacy class name.
    * @param    modes
    *           The argument modes.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static Object invokeStaticMethod(character   clsName, 
                                           String      name, 
                                           String      modes, 
                                           character[] ptypes, 
                                           Object[]    args)
   {
      return invokeStaticMethod(clsName, new character(name), modes, ptypes, args);
   }

   /**
    * Invoke the target static method in the given legacy class.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    clsName
    *           The target legacy class name.
    * @param    modes
    *           The argument modes.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static Object invokeStaticMethod(String      clsName, 
                                           character   name, 
                                           String      modes, 
                                           character[] ptypes, 
                                           Object[]    args)
   {
      return invokeStaticMethod(new character(clsName), name, modes, ptypes, args);
   }

   /**
    * Invoke the target static method in the given legacy class.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    clsName
    *           The target legacy class name.
    * @param    modes
    *           The argument modes.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static Object invokeStaticMethod(character   clsName, 
                                           character   name, 
                                           String      modes, 
                                           character[] ptypes, 
                                           Object[]    args)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      Object[] oargs = resolveArgs(modes, ptypes, args, true);
      return ObjectOps.invoke(clsName, name, null, oargs);
   }

   /**
    * Invoke the target method in the given legacy class instance.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    ref
    *           The target instance.
    * @param    name
    *           The target legacy method name.
    * @param    modes
    *           The argument modes.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static Object invokeMethod(object<? extends _BaseObject_> ref, 
                                     String                         name,
                                     String                         modes, 
                                     character[]                    ptypes, 
                                     Object[]                       args)
   {
      return invokeMethod(ref, new character(name), modes, ptypes, args);
   }

   /**
    * Invoke the target method in the given legacy class instance.
    * <p>
    * The argument types must be specified, so FWD runtime can build the proper argument for i.e.
    * TABLE-HANDLE or DATASET-HANDLE.
    * 
    * @param    ref
    *           The target instance.
    * @param    name
    *           The target legacy method name.
    * @param    modes
    *           The argument modes.
    * @param    ptypes
    *           The argument data-types.
    * @param    args
    *           The arguments.
    */
   public static Object invokeMethod(object<? extends _BaseObject_> ref, 
                                     character                      name,
                                     String                         modes, 
                                     character[]                    ptypes, 
                                     Object[]                       args)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      Object[] oargs = resolveArgs(modes, ptypes, args, true);
      return ObjectOps.invoke(ref, name, null, oargs);
   }

   /**
    * Get the argument at the specified position, as a <code>TABLE-HANDLE</code>.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static handle getTableHandle(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (handle) toLegacy(wa, idx);
   }
   
   /**
    * Set the argument at the specified position, as a <code>TABLE-HANDLE</code>.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setTableHandle(int idx, handle val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_TABLE_HANDLE, val);
   }

   /**
    * Get the argument at the specified position, as a <code>DATASET-HANDLE</code>.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static handle getDataSetHandle(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (handle) toLegacy(wa, idx);
   }
   
   /**
    * Set the argument at the specified position, as a <code>DATASET-HANDLE</code>.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setDataSetHandle(int idx, handle val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_DATASET_HANDLE, val);
   }

   /**
    * Get the argument at the specified position, as a {@link memptr}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static memptr getMemptr(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (memptr) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link memptr}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setMemptr(int idx, memptr val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_MEMPTR, val);
   }

   /**
    * Get the argument at the specified position, as a {@link integer}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static integer getInteger(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (integer) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link integer}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setInteger(int idx, integer val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_INTEGER, val);
   }

   /**
    * Get the argument at the specified position, as a {@link int64}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static int64 getInt64(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (int64) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link int64}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setInt64(int idx, int64 val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_INT64, val);
   }

   /**
    * Get the argument at the specified position, as a {@link decimal}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static decimal getDecimal(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (decimal) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link decimal}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setDecimal(int idx, decimal val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_DECIMAL, val);
   }

   /**
    * Get the argument at the specified position, as a {@link character}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static character getCharacter(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (character) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link character}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setCharacter(int idx, character val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_CHARACTER, val);
   }

   /**
    * Get the argument at the specified position, as a {@link longchar}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static longchar getLongchar(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (longchar) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link longchar}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setLongchar(int idx, longchar val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_LONGCHAR, val);
   }

   /**
    * Get the argument at the specified position, as a {@link logical}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static logical getLogical(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (logical) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link logical}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setLogical(int idx, logical val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_LOGICAL, val);
   }

   /**
    * Get the argument at the specified position, as a {@link date}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static date getDate(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (date) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link date}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setDate(int idx, date val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_DATE, val);
   }

   /**
    * Get the argument at the specified position, as a {@link datetime}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static datetime getDatetime(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (datetime) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link datetime}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setDatetime(int idx, datetime val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_DATETIME, val);
   }

   /**
    * Get the argument at the specified position, as a {@link datetimetz}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static datetimetz getDatetimetz(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (datetimetz) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link datetimetz}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setDatetimetz(int idx, datetimetz val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_DATETIMETZ, val);
   }

   /**
    * Get the argument at the specified position, as a {@link recid}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static recid getRecid(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (recid) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link recid}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setRecid(int idx, recid val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_RECID, val);
   }

   /**
    * Get the argument at the specified position, as a {@link raw}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static raw getRaw(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (raw) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link raw}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setRaw(int idx, raw val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_RAW, val);
   }

   /**
    * Get the argument at the specified position, as a {@link rowid}.
    * 
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   The argument's value.
    */
   public static rowid getRowid(int idx)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      return (rowid) toLegacy(wa, idx);
   }

   /**
    * Set the argument at the specified position, as a {@link rowid}.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setRowid(int idx, rowid val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, LegacyJavaAppserverApi.TYPE_ROWID, val);
   }
   
   /**
    * Set the argument at the specified position.
    * 
    * @param    idx
    *           The 1-based argument index.
    * @param    val
    *           The argument's value.
    */
   public static void setArgument(int idx, BaseDataType val)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      validateIndex(wa, idx, true);

      idx = idx - 1;
      
      wa.arguments[idx] = toJava(wa, idx, -1, val);
   }
   
   /**
    * Store all the OUTPUT or INPUT-OUTPUT arguments received in the given array to be transferred back to the
    * caller. 
    * 
    * @param    args
    *           The arguments.
    */
   public static void setArguments(Object... args)
   {
      WorkArea wa = local.get();
      
      securityCheck(wa);

      if (args.length != wa.arguments.length)
      {
         ErrorManager.recordOrThrowError(-1, "Argument length must be " + wa.arguments.length);
         return;
      }
      
      for (int i = 0; i < args.length; i++)
      {
         if (wa.boundArguments[i])
         {
            ErrorManager.recordOrShowError(-1,
                                           "Argument at index " + (i + 1) + 
                                           " is already associated as parameter - skipping.",
                                           false);
            
            // bound arguments will be skipped.
            continue;
         }
         
         Object arg = args[i];
         if (arg instanceof jobject)
         {
            jobject j = (jobject) arg;
            arg = j.isUnknown() ? null : ((jobject) arg).ref();
         }

         wa.arguments[i] = toJava(wa, i, -1, arg);
      }
   }

   /**
    * For the {@link LegacyJavaAppserverClient#invokeWithArgs} call, initialize the argument state, before the
    * call was performed.
    * 
    * @param    argTypes
    *           The argument types.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    */
   static void initializeArguments(int[] argTypes, String modes, Object[] args)
   {
      WorkArea wa = local.get();
      wa.withArgsCall = true;
      wa.argTypes = argTypes;
      wa.arguments = args;
      wa.originalArguments = new Object[args.length];
      System.arraycopy(args, 0, wa.originalArguments, 0, args.length);
      wa.boundArguments = new boolean[args.length];
      wa.modes = modes;
      
      // validation for unsupported types
      for (int i = 0; i < wa.arguments.length; i++)
      {
         String argTypeErr = null;
         
         int type = wa.argTypes[i];
         switch (type)
         {
            // complex types
            case LegacyJavaAppserverApi.TYPE_TABLE_HANDLE:
            case LegacyJavaAppserverApi.TYPE_DATASET_HANDLE:
            case LegacyJavaAppserverApi.TYPE_MEMPTR:
            // simple types
            case LegacyJavaAppserverApi.TYPE_INTEGER:
            case LegacyJavaAppserverApi.TYPE_INT64:
            case LegacyJavaAppserverApi.TYPE_DECIMAL:
            case LegacyJavaAppserverApi.TYPE_CHARACTER:
            case LegacyJavaAppserverApi.TYPE_LONGCHAR:
            case LegacyJavaAppserverApi.TYPE_LOGICAL:
            case LegacyJavaAppserverApi.TYPE_DATE:
            case LegacyJavaAppserverApi.TYPE_DATETIME:
            case LegacyJavaAppserverApi.TYPE_DATETIMETZ:
            case LegacyJavaAppserverApi.TYPE_RECID:
            case LegacyJavaAppserverApi.TYPE_RAW:
            case LegacyJavaAppserverApi.TYPE_ROWID:
               break;
               
            // unsupported types
            case LegacyJavaAppserverApi.TYPE_BLOB:
               argTypeErr = "BLOB";
               break;
            case LegacyJavaAppserverApi.TYPE_CLOB:
               argTypeErr = "CLOB";
               break;
            case LegacyJavaAppserverApi.TYPE_HANDLE:
               argTypeErr = "HANDLE";
               break;
            case LegacyJavaAppserverApi.TYPE_UNKNOWN:
               argTypeErr = "UNKNOWN";
               break;
            case LegacyJavaAppserverApi.TYPE_DATASET:
               argTypeErr = "DATASET";
               break;
            case LegacyJavaAppserverApi.TYPE_TABLE:
               argTypeErr = "TABLE";
               break;
               
            default:
               argTypeErr = "[undefined type " + type + "]";
               break;
         }
         
         if (argTypeErr != null)
         {
            ErrorManager.recordOrThrowError(-1, "Argument type " + argTypeErr + " is not supported.");
            return;
         }
      }
   }

   /**
    * For the {@link LegacyJavaAppserverClient#invokeWithArgs} call, clear the argument state, once the call
    * has finished.
    */
   static void clearArguments()
   {
      WorkArea wa = local.get();
      
      // all arguments which are not bound need to be cleaned up
      for (int i = 0; i < wa.arguments.length; i++)
      {
         if (wa.boundArguments[i])
         {
            // bound arguments are cleaned automatically
            continue;
         }

         int type = wa.argTypes[i];
         switch (type)
         {
            // complex types
            case LegacyJavaAppserverApi.TYPE_TABLE_HANDLE:
               if (wa.originalArguments[i] != wa.arguments[i])
               {
                  // clean original first
                  TableParameter table = (TableParameter) wa.originalArguments[i];
                  handle htable = table.getTableHandle();
                  if (htable != null && htable._isValid())
                  {
                     HandleOps.delete(htable);
                  }
               }

               // clean argument second
               TableParameter table = (TableParameter) wa.arguments[i];
               handle htable = table.getTableHandle();
               if (htable != null && htable._isValid())
               {
                  HandleOps.delete(htable);
               }
               break;

            case LegacyJavaAppserverApi.TYPE_DATASET_HANDLE:
               if (wa.originalArguments[i] != wa.arguments[i])
               {
                  // clean original first
                  DataSetParameter dataset = (DataSetParameter) wa.originalArguments[i];
                  handle hdataset = dataset.get();
                  if (hdataset != null && hdataset._isValid())
                  {
                     HandleOps.delete(hdataset);
                  }
               }
               
               // clean argument second
               DataSetParameter dataset = (DataSetParameter) wa.arguments[i];
               handle hdataset = dataset.get();
               if (hdataset != null && hdataset._isValid())
               {
                  HandleOps.delete(hdataset);
               }
               break;

            case LegacyJavaAppserverApi.TYPE_MEMPTR:
               if (wa.originalArguments[i] != wa.arguments[i])
               {
                  // clean original first
                  ((memptr) wa.originalArguments[i]).setLength(0);
               }
               
               // clean argument second
               ((memptr) wa.arguments[i]).setLength(0);
               break;
         }
      }
      
      wa.withArgsCall = false;
      wa.argTypes = null;
      wa.arguments = null;
      wa.originalArguments = null;
      wa.boundArguments = null;
      wa.modes = null;
      
      for (int i = 0; i < wa.resources.size(); i++)
      {
         WrappedResource res = wa.resources.get(i);
         if (res.valid())
         {
            ErrorManager.nestedSilent(() -> ((Deletable) res).delete());
         }
      }
      wa.resources.clear();
   }
   
   /**
    * For a setter, convert the given argument to its representation so it can be sent back to the remote 
    * side.
    * 
    * @param    wa
    *           The context-local instance.
    * @param    idx
    *           The 0-based argument index.
    * @param    argType
    *           The argument type, one of the {@link LegacyJavaAppserverApi#integer()} APIs.
    * @param    arg
    *           The argument instance.
    *           
    * @return   The argument, in the form where it can be stored in {@link WorkArea#arguments}, to be sent
    *           back to the remote side.
    */
   private static Object toJava(WorkArea wa, int idx, int argType, Object arg)
   {
      // TODO: extent
      
      if (wa.modes.charAt(idx) == 'I')
      {
         ErrorManager.recordOrShowError(-1,
                                        "INPUT argument " + (idx + 1) + 
                                        " from the remote call can not be changed.",
                                        false);
         return wa.arguments[idx];
      }
      
      if (arg == null)
      {
         return null;
      }

      int type = wa.argTypes[idx];

      if (argType != -1 && argType != type)
      {
         ErrorManager.recordOrThrowError(-1, 
                                         "Attempting to set argument as type " + argType + 
                                         " but the remote parameter is of type " + type + ".");
         return arg;
      }
      
      switch (type)
      {
         // complex types
         case LegacyJavaAppserverApi.TYPE_TABLE_HANDLE:
            TableParameter tp = (TableParameter) wa.arguments[idx];
            tp.getResultSet().setResultSet(null);
            OutputTableHandleCopier tcopier = new OutputTableHandleCopier(tp, (handle) arg);
            tcopier.finished();
            return tp;
         case LegacyJavaAppserverApi.TYPE_DATASET_HANDLE:
            DataSetParameter dsp = (DataSetParameter) wa.arguments[idx];
            OutputDataSetHandleCopier dscopier = new OutputDataSetHandleCopier(dsp, (handle) arg);
            dscopier.finished();
            return dsp;

         case LegacyJavaAppserverApi.TYPE_MEMPTR:
            return arg;

         // simple types
         case LegacyJavaAppserverApi.TYPE_INTEGER:
         case LegacyJavaAppserverApi.TYPE_INT64:
         case LegacyJavaAppserverApi.TYPE_DECIMAL:
         case LegacyJavaAppserverApi.TYPE_CHARACTER:
         case LegacyJavaAppserverApi.TYPE_LONGCHAR:
         case LegacyJavaAppserverApi.TYPE_LOGICAL:
         case LegacyJavaAppserverApi.TYPE_DATE:
         case LegacyJavaAppserverApi.TYPE_DATETIME:
         case LegacyJavaAppserverApi.TYPE_DATETIMETZ:
         case LegacyJavaAppserverApi.TYPE_RECID:
         case LegacyJavaAppserverApi.TYPE_RAW:
         case LegacyJavaAppserverApi.TYPE_ROWID:
            return arg;
      }
      
      ErrorManager.recordOrThrowError(-1, "Argument type " + type + " is not supported.");

      return null;
   }

   /**
    * Get the argument at the given 1-based index in a {@link BaseDataType} form, used by the legacy converted
    * code.
    * @param    wa
    *           The context-local instance.
    * @param    idx
    *           The 1-based argument index.
    *           
    * @return   See above.
    */
   private static BaseDataType toLegacy(WorkArea wa, int idx)
   {
      validateIndex(wa, idx);

      idx = idx - 1;

      // TODO: extent
      
      int type = wa.argTypes[idx];
      Object arg  = wa.arguments[idx];
      
      switch (type)
      {
         // complex types
         case LegacyJavaAppserverApi.TYPE_TABLE_HANDLE:
            handle tth = new handle();
            TemporaryBuffer.createDynamicTable((TableParameter) arg, tth, true, false);
            if (tth._isValid())
            {
               wa.resources.add(tth.getResource());
            }
            return tth;
         case LegacyJavaAppserverApi.TYPE_DATASET_HANDLE:
            handle dsh = new handle();
            DataSet.createDynamicDataSet((DataSetParameter) arg, dsh, true, false);
            if (dsh._isValid())
            {
               wa.resources.add(dsh.getResource());
            }
            return dsh;

         case LegacyJavaAppserverApi.TYPE_MEMPTR:
            return new memptr((memptr) arg);

         // simple types
         case LegacyJavaAppserverApi.TYPE_INTEGER:
            return new integer((integer) arg);
         case LegacyJavaAppserverApi.TYPE_INT64:
            return new int64((int64) arg);
         case LegacyJavaAppserverApi.TYPE_DECIMAL:
            return new decimal((decimal) arg);
         case LegacyJavaAppserverApi.TYPE_CHARACTER:
            return new character((character) arg);
         case LegacyJavaAppserverApi.TYPE_LONGCHAR:
            return new longchar((longchar) arg);
         case LegacyJavaAppserverApi.TYPE_LOGICAL:
            return new logical((logical) arg);
         case LegacyJavaAppserverApi.TYPE_DATE:
            return new date((date) arg);
         case LegacyJavaAppserverApi.TYPE_DATETIME:
            return new datetime((datetime) arg);
         case LegacyJavaAppserverApi.TYPE_DATETIMETZ:
            return new datetimetz((datetimetz) arg);
         case LegacyJavaAppserverApi.TYPE_RECID:
            return new recid((recid) arg);
         case LegacyJavaAppserverApi.TYPE_RAW:
            return new raw((raw) arg);
         case LegacyJavaAppserverApi.TYPE_ROWID:
            return new rowid((rowid) arg);

      }
      
      ErrorManager.recordOrThrowError(-1, "Argument type " + type + " is not supported.");
      
      return null;
   }

   /**
    * Resolve the arguments passed to {@link #invokeExternalProgram}, {@link #invokeStaticMethod} or 
    * {@link #invokeMethod}, so that they can be passed to the target invocation.
    *  
    * @param    modes
    *           The argument modes.
    * @param    ptypes
    *           The string-representation of the argument types.
    * @param    args
    *           The arguments.
    * @param    isOO
    *           Flag indicating if this is a OO call.
    *           
    * @return   See above.
    */
   private static Object[] resolveArgs(String modes, character[] ptypes, Object[] args, boolean isOO)
   {
      if (modes != null && modes.length() != args.length)
      {
         ErrorManager.recordOrThrowError(-1, "Argument mode length and argument length does not match!");
         return args;
      }
      
      if (isOO && modes == null)
      {
         ErrorManager.recordOrThrowError(-1, "For a OO call, the argument modes must be specified!");
         return args;
      }
      
      if (modes != null)
      {
         modes = modes.toUpperCase();
         for (int i = 0; i < modes.length(); i++)
         {
            char c = modes.charAt(i);
            if (c != 'I' && c != 'U' && c != 'O')
            {
               ErrorManager.recordOrThrowError(-1, "Invalid argument mode at position " + (i + 1) + ": " + c);
               return args;
            }
         }
      }
      
      Object[] oargs = new Object[args.length];
      for (int i = 0; i < args.length; i++)
      {
         String ptype = ptypes[i].getValue();
         Object arg = args[i];
         if (arg instanceof jobject)
         {
            arg = ((jobject) arg).ref();
         }
         
         char pmode = (modes == null ? (char) -1 : modes.charAt(i));
         
         switch (ptype.toUpperCase())
         {
            // complex types
            case "TABLE-HANDLE":
               if (isOO)
               {
                  switch (pmode)
                  {
                     case 'I':
                        arg = new InputTableHandle((handle) arg);
                        break;
                     case 'O':
                        arg = new OutputTableHandle((handle) arg);
                        break;
                     case 'U':
                        arg = new InputOutputTableHandle((handle) arg);
                        break;
                  }
               }
               else
               {
                  arg = new TableParameter((handle) arg);
               }
               break;
            case "DATASET-HANDLE":
               if (isOO)
               {
                  switch (pmode)
                  {
                     case 'I':
                        arg = new InputDataSetHandle((handle) arg);
                        break;
                     case 'O':
                        arg = new OutputDataSetHandle((handle) arg);
                        break;
                     case 'U':
                        arg = new InputOutputDataSetHandle((handle) arg);
                        break;
                  }
               }
               else
               {
                  arg = new DataSetParameter((handle) arg);
               }
               break;

            case "MEMPTR":
               // do nothing
               break;
            
            // simple types
            case "INTEGER":
            case "INT64":
            case "DECIMAL":
            case "CHARACTER":
            case "LONGCHAR":
            case "LOGICAL":
            case "DATE":
            case "DATETIME":
            case "DATETIMETZ":
            case "DATETIME-TZ":
            case "RECID":
            case "RAW":
            case "ROWID":
               // do nothing
               break;

            // unsupported types
            case "BLOB":
            case "CLOB":
            case "HANDLE":
            case "UNKNOWN":
            case "DATASET":
            case "TABLE":
               ErrorManager.recordOrThrowError(-1, "Parameter type " + ptype + " is not supported!");
               break;
               
            default:
               ErrorManager.recordOrThrowError(-1, "Parameter type " + ptype + " is unknown!");
               break;
         }
         
         oargs[i] = arg; 
      }
      return oargs;
   }

   /**
    * Validate the given index to be in the {@link WorkArea#arguments} range.
    * 
    * @param    wa
    *           The context-local instance.
    * @param    idx
    *           The 1-based index.
    */
   private static void validateIndex(WorkArea wa, int idx)
   {
      validateIndex(wa, idx, false);
   }

   /**
    * Validate the given index to be in the {@link WorkArea#arguments} range.
    * 
    * @param    wa
    *           The context-local instance.
    * @param    idx
    *           The 1-based index.
    * @param    validateBound
    *           Flag indicating if the {@link WorkArea#boundArguments} needs to be false.
    */
   private static void validateIndex(WorkArea wa, int idx, boolean validateBound)
   {
      if (idx < 1 || idx > wa.arguments.length)
      {
         ErrorManager.recordOrThrowError(-1, "LegacyOpenClientCaller: index " + idx + 
                                             " is out of range [1.." + wa.arguments.length + "].");
         return;
      }
      
      if (validateBound && wa.boundArguments[idx - 1])
      {
         ErrorManager.recordOrThrowError(-1, "Parameter " + idx + " is already associated as parameter.");
         return;
      }
   }
   
   /**
    * Check if the call is being performed in the context of the OpenClient call.  This means that 
    * {@link ProcedureHelper#_thisProcedure()} must be the same as {@link ProcedureHelper#_rootProcedure()}.
    * 
    * @param    wa
    *           The context-local instance.
    * @param    root
    *           Flag indicating we must be in the root external block.
    */
   private static void securityCheck(WorkArea wa)
   {
      securityCheck(wa, false);
   }
   
   /**
    * Check if the call is being performed in the context of the OpenClient call.  This means that 
    * {@link ProcedureHelper#_thisProcedure()} must be the same as {@link ProcedureHelper#_rootProcedure()}.
    * 
    * @param    wa
    *           The context-local instance.
    * @param    root
    *           Flag indicating we must be in the root external block.
    */
   private static void securityCheck(WorkArea wa, boolean root)
   {
      // all API calls must be done from the context of the root caller, and only if the root caller is
      // an 'invokeWithArgs'
      if (!wa.withArgsCall)
      {
         ErrorManager.recordOrThrowError(-1, "LegacyOpenClientCaller APIs can be accessed only if the remote " +
                                             "call was performed via 'invokeWithArgs'!");
         return;
      }
      
      Object thisProc = wa.pm._thisProcedure();
      Object rootProc = wa.pm._rootProcedure();
      if (thisProc != rootProc)
      {
         ErrorManager.recordOrThrowError(-1, "LegacyOpenClientCaller APIs can be accessed only from the " + 
                                             "context of the invoked program: " + rootProc.getClass());
         return;
      }

      if (root && TransactionManager.getNestingLevel() > 3)
      {
         ErrorManager.recordOrThrowError(-1, 
                                         "'asDataSetParameter' or 'asTableParameter' can be used only from " +
                                         "the root external program for the proxy call.");
         return;
      }
   }

   /** 
    * Stores global data relating to the state of the current context.
    */
   private static class WorkArea
   {
      /** Helper to use the {@link ProcedureManager} without any context local lookups. */
      private ProcedureHelper pm = ProcedureManager.getProcedureHelper();
      
      /** Flag indicating that the root call is via the {@link LegacyJavaAppserverClient#invokeWithArgs}. */
      private boolean withArgsCall;

      /** The received argument types. */
      private int[] argTypes = null;

      /** The received argument modes. */
      private String modes = null;

      /** The received argument. */
      private Object[] arguments = null;
      
      /** The original references for the arguments. */
      private Object[] originalArguments = null;
      
      /** Flags indicating which [INPUT-]OUTPUT arameters have been bound to a 4GL converted variable. */
      private boolean[] boundArguments = null;
      
      /** List of resources created by {@link LegacyOpenClientCaller#toLegacy}. */
      private List<WrappedResource> resources = new ArrayList<>();
   }
}