ControlFlowOps.java
/*
** Module : ControlFlowOps.java
** Abstract : Progress compatible control flow helper methods
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 GES 20050616 @21506 Created initial version, supporting PAUSE.
** 002 SIY 20050815 @22152 Added invoke() method used by RUN VALUE().
** 003 GES 20050829 @22381 Added error condition processing.
** 004 GES 20060117 @23955 Removed PAUSE (it now resides in UI code).
** 005 GES 20060123 @24026 Added return-value support as context-local.
** 006 GES 20060314 @25063 Fixed exception processing in invoke().
** 007 ECF 20060329 @25259 Removed fillInStackTrace calls to exceptions
** caught and re-thrown in invoke(). These were
** masking the true origins of the exceptions.
** 008 GES 20060427 @25774 Safety code to allow wrapper classes to be
** passed as a program name.
** 009 GES 20061030 @30804 Better processing of condition exceptions.
** 010 GES 20070220 @32194 Added support for RUN IN.
** 011 GES 20080619 @38885 If a RUN VALUE (actually any RUN statement)
** cannot find the procedure to execute, a STOP
** condition is raised (after some error text
** has been displayed on the terminal), not an
** ERROR. This means that silent error mode does
** not apply, the exception is always thrown and
** the ERROR-STATUS handle is not updated.
** 012 GES 20080619 @39166 Fix for RUN VALUE of an internal procedure in
** compact mode.
** 013 SIY 20080804 @39280 Changed signature of simple invoke(). This
** enables correct calls to internal procedures
** without redundant instantiation of class and
** greatly simplifies compact mode.
** Some minor cleanups and potential NPE fix.
** 014 GES 20090422 @41906 Converted to standard string formatting.
** 015 GES 20101005 Added a stub (non-functional) for a RUN statement
** that references a PROCEDURE EXTERNAL definition.
** Added a stub for DELETE PROCEDURE.
** 016 CA 20130116 Major rework to provide support for IN handle and
** IN SUPER functions and procedures, plus RUN SUPER
** and SUPER statements.
** 017 CA 20130126 Added support for RELEASE EXTERNAL (converts to
** a ControlFlowOps.release(<library>) call, which
** is only a stub at this time).
** 018 CA 20130213 Fixed some cases of extent parameter validation.
** 019 CS 20130213 Added APIs supporting the ON server and ASYNC clauses for the RUN
** statement.
** 020 CA 20130223 Added conversion support for the RUN ... SET hPort ON hWebServer stmt
** to invoke web services.
** 021 CA 20130315 Added support for cases when a WrappedResource is passed to a handle
** parameter.
** 022 OM 20130404 Fixed unknown parameter in function/procedure calls.
** Abort function/procedure call if errors occurred when converting
** compatible parameters (ex: 32bit overflow from decimal/int64).
** 023 CA 20130529 Added support for RUN ... [PERSISTENT [handle]] ON SERVER server and
** RUN ... IN remoteProcHandle.
** 024 CA 20130708 Fixed validation of arrays with non-BDT components (these will be
** validated by attempting to instantiate a wrapper using a c'tor which
** accepts this array as a parameter).
** 025 CA 20130813 Added support for async procedure calls.
** 026 SVL 20131203 Changes caused by renaming of Nameable.get/setName to Nameable.name.
** 027 CA 20131116 Added support for the FUNCTION ... MAP TO clause. Fixed runtime for
** RUN SUPER and SUPER() calls, in search-target and search-self modes.
** 028 EVK 20131204 Made enum ArgValidationErrors public. Added method
** reachableInternalEntry() to check reachability of procedure or
** function in 4gl handle.
** 029 CA 20131220 Refactored the async invocation code and the remote invocation code
** to support both web service and appserver requests.
** 030 GES 20140116 Added support for native library calls.
** 031 CA 20140218 Fixed SOURCE- and TARGET-PROCEDURE when invoking external programs.
** Error messages must use the program name as it was used at the RUN
** statement. Internally, the absolute name must be used (i.e. when
** invoking SourceNameMapper APIs).
** 032 ECF 20140613 Replaced renamed RecordBuffer method in getSignature.
** 033 MAG 20140626 Prevent fault messages to be displayed twice when invoke a web
** service.
** 034 VMN 20140702 Added method convertValue() for correct processing unknown value
** in invoke... methods.
** 035 HC 20140613 Improved extent parameter support and added logging.
** 036 SVL 20140709 Handle InvalidParameterConditionException in invokeImpl.
** 037 CA 20141106 Changed H035 - log only exceptions which are not propagated.
** 038 CA 20150504 A persistent procedure needs to be saved in its specified handle only
** if it completes properly.
** 039 ECF 20150527 Added a cache to InternalEntryCaller as a performance enhancement.
** 040 OM 20150624 Improved procedure parameters type checking for OUT and IN-OUT modes.
** Added implicit conversion for parameters.
** 041 OM 20150722 In calls to ControlFlowOps.runSuper(), the modes are unknown.
** 042 CA 20150813 Added BlockManager.markPendingDynCall to initializeExternalProcedure.
** This is required so that the remote procedure call is not interpreted
** like a java-style method call, when a proxy procedure is used by an
** appserver.
** 043 ECF 20150815 Performance improvements in CacheKey c'tor and getSignature.
** 044 CA 20151024 Fixed assignment of DYNAMIC-FUNCTION ... NO-ERROR when the function
** can not be found.
** 045 OM 20161012 Fixed variable parameter passing in OUTPUT & INPUT-OUTPUT mode.
** 046 CA 20151219 In case of ERROR conditions raised by shared resource management,
** if the caller has a NO-ERROR clause, then the condition must NOT be
** raised, but logged; also, the external program execution must stop,
** and return back to the caller immediately.
** 047 OM 20160212 Called cleanupPending() for various Managers on procedure
** instantiation failure.
** 048 CA 20160404 Enhanced the appserver invocations, to allow implicit or explicit
** termination of requests being executing on a certain connection.
** 049 CA 20160418 Added infrastructure for message passing from converted code to a
** customer-specific application or calls from the customer-specific
** application into the converted code (i.e. invoke external programs,
** procedures or functions). Can be used only when the P2J client runs
** embedded in a customer-specific application.
** 050 OM 20160302 ExternalProgramResolver resolves .r code filenames and resolves them
** as the .p/.w source code procedure.
** 051 OM 20160527 SourceNam eMapper.convertName() changed to convertNameToClass().
** 052 HC 20160720 RUN, DYNAMIC-FUNCTION and function invocation must not show an error
** when the given target handle is invalid and NO-ERROR is specified.
** 053 CA 20160728 Fixed undoable support for data in external programs ran persistent.
** 054 HC 20160817 Fixed PUBLISH to resolve the target internal procedure in the
** subscribed procedure as well as in its SUPER procedures or SESSION
** procedures.
** Fixed RUN invocation logic to set up the correct TARGET-PROCEDURE
** when the resolved internal procedure is found in a SUPER procedure.
** 055 HC 20160824 Fixed a regression from the previous change, FUNCTION ... IN
** statement did not properly resolve the function from the super
** procedures.
** 056 HC 20160901 Fixed error handling of procedure handle validation in a RUN or
** function invocation, a regression introduced in H052.
** 057 ECF 20160912 Flattened invocation calling hierarchy. Although this is somewhat
** less maintainable due to argument list duplication across multiple
** methods, it avoids several layers of method calls in heavily executed
** code paths.
** 058 CA 20161020 Fixed case-insensitive resolution with .r (rcode) ext program names.
** 059 CA 20170328 Fixed a problem with parameter validation when invoked by a remote
** non-P2J side (i.e. API invoked by an embedded client).
** 060 GES 20171206 Switch to ErrorManager.silent(). Added TODOs.
** 061 ECF 20171227 Performance improvement.
** GES 20180102 Clarified error handling comments.
** 062 EVL 20180202 Filter pre/post steps for native library calls.
** 063 CA 20180510 Delegate lockWindowUpdate user32.dll calls to LT.disableRedraw.
** CA 20180521 The IN SUPER function definition in the current file may provide a
** return type different than its actual implementation - this affects
** function calls, so that it must not fail if return types differ.
** 064 CA 20181029 Added support for CALL dynamic invoke.
** 065 CA 20181112 Added some error processing related to RUN ... TRANSACTION DISTINCT.
** Fixed a case of RUN ... PERSISTENT SET ... REMOTE ASYNC, where the
** remote handle is the SESSION.
** function calls, so that it must not fail if return types differ.
** 066 EVL 20181102 Fix for persistent procedure to be removed when error happens during
** it's execution.
** 067 CA 20190122 Added legacy class instantiation support.
** 20190128 Added legacy class initialization support (static c'tor).
** CA 20190201 When both 'IN handle' and local func definition exist, local def
** must not be hidden. Callers of this function will resolve it via
** the 'IN handle' forward definition.
** CA 20190220 Added runtime support for DYNAMIC-INVOKE, DYNAMIC-NEW.
** HC 20190226 When procedure is invoked persistently the target handle with the
** persistent procedure must be assigned before the procedure is
** invoked.
** CA 20190324 Optimizations of InternalEntryResolver, to avoid multiple hits to
** SourceNameMapper.
** CA 20190325 Implemented ProcedureHelper instead of direct usage of the static
** API. This allows the elimination of context local usage in
** ProcedureManager.
** Refactored some APIs into ControlFlowOpsHelper, to be able to use
** this class without a context-local hit.
** ECF 20190326 Replace String.format with less expensive concatenation in a commonly
** used error message.
** 068 CA 20190508 OutputParameter.wrap must be fully generic.
** CA 20190526 Fixed runtime and conversion support for legacy class properties used
** as OUTPUT or INPUT-OUTPUT arguments.
** 069 CA 20190613 A legacy class member can be invoked remotely, as an entry point.
** 070 CA 20190628 Added RUN SINGLE-RUN and RUN SINGLETON support.
** 071 CA 20190710 Fixed some issue with legacy object arguments.
** CA 20190723 All the static members must be 'get' before the static class c'tor is
** executed, so they get registered with the surrogate instance for the
** static class.
** 072 IAS 20190926 Fixed Legacy Class initialization
** 073 CA 20190927 Added support for direct Java access from 4GL code.
** 074 CA 20191119 Track the executing legacy class, to be able to register any buffer
** to its source definition class. Fixed an issue with disambiguating
** a legacy OE class method, during dynamic invoke.
** CA 20191211 Fixed a NPE for async requests.
** CA 20191212 Fixed constructor lookup (look for super-class or interface match).
** 075 CA 20200213 Added support for RUN ... AS-THREAD [SET ht]. statement.
** 076 CA 20200412 A dynamic OO method invoke from an instance context can look for a
** static method (this is allowed to solve a parser issue, where a
** non-dynamic method call is marked as dynamic, as the caller as BDT
** arguments and we need to let the runtime to resolve the target).
** 077 CA 20200324 A fix for legacy object instantiation - use the ctor method found
** after argument validation.
** 078 CA 20200330 More fixes related to OE-compatible error management, in constructor
** case and 'implicit' 4GL ERROR conditions.
** 079 CA 20200427 Refactored so that the remote appserver calls use InvokeConfig, to
** allow transfer of the SERVER:REQUEST-INFO to Agent's
** SESSION:CURRENT-REQUEST-INFO.
** CA 20200514 Added DATETIME-TZ alias to supported types.
** 080 ME 20200521 Make sure we register the full class hierarchy for qualified classes
** used as parameters in constructors as it might have not been registered.
** 081 VVT 20200805 Comhandle input procedure parameters can now be converted to handles.
** See #4841 and #4831 issues.
** 082 CA 20200827 Reworked asynchronous invocations to perform all context-local work on the
** Conversation thread, and let only the actual invocation be performed in an
** AssociatedThread.
** 083 AIL 20200622 Made use of the new procedure delete signature.
** CA 20200811 SUPER() function has as first argument the caller's return type.
** GES 20200813 Removed come context-local lookups for ErrorManager.
** AIL 20200901 Parameter type check is made on a clean pendingError flag.
** CA 20200927 Added ControlFlowOpsHelper.setReturnValue(String).
** CA 20200927 Avoid context-local lookups by relying on the helper instance.
** CA 20201003 Replaced Guava identity HashSet with Collections.newSetFromMap(IdentityHashMap).
** RFB 20201015 Since thisProcedure can return null, validHandle needed to protect the error
** message buildup. Ref. 4936.
** SVL 20210127 Added isReturnValueSetInRootScope.
** SVL 20210202 Assign return value to an invocation result only if the value was set during this
** call.
** CA 20210203 Fixed dynamic vs non-dynamic extent argument validation for OO calls.
** CA 20210216 Arithmetic operations are always int64 - this requires special processing when
** passing such an expression as an argument, so the conversion-time type of the
** expression is preserved.
** OM 20210309 Decreased the amount of "incompatible type" messages printed to log.
** CA 20210421 When validating the arguments, convert them to a BDT instance if they are Java
** types as the call passed Java literals.
** CA 20210511 A RUN statement executed from the global block will always target an external
** program executed.
** CA 20210428 A DYNAMIC-FUNCTION/FUNCTION call must raise an ERROR condition only and only if
** the failure is from a remote appserver agent call, and not a call from within
** the appserver agent execution.
** CA 20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
** initialized at the method's execution, and not at the caller's arguments.
** AIL 20210615 Check if handle should be set before or after run ... persistent set.
** CA 20210810 Raised error 6456 if an empty string is passed to 'RUN ... IN handle'.
** HC 20211011 Implemented i18n support.
** CA 20211025 If argument validation failed, then cleanup all pending resources.
** CA 20211222 Fixed a bug in invokeLegacyMethod - if there is an exception, throw it, so it can
** be processed, and not just return null.
** CA 20220120 Performance improvement for pushing a new calee scope, keep the already resolved
** internal entry legacy name.
** CA 20220206 returnValueSetInRootScope must be set only when the RETURN is executed from the
** root nesting level.
** AL2 20220319 Added value proxy logic: wrap for dynamic and unwrap for static.
** RFB 20220712 Use a new tm method isRootExternal() instead of isRootNestingLevel() to determine
** nesting level in setReturnValue methods so that assignments in a DO determine it
** properly. Ref #6587.
** VVT 20220722 Added method isHandleValidProcedure() to support the fix for #6606.
** VVT 20220722 Method isHandleValidProcedure() removed as unneeded. See #6606-12.
** TW 20220803 Error-status:error must be set for return error in method, while return error
** in old-style ABL function must remain the same. Refs: #6567.
** TW 20220806 Error-status:error must be set for return error in dynamic-invoke of method
** of ABL class. Fixes xfer testc ErrorStatusFlowTester.w Test06.
** TW 20220817 Error-status:error must be set for return error in static method.
** CA 20220930 Refactored the callback invocation to be performed via a call-site and
** InvokeConfig, to allow caching of the resolved target.
** CA 20220901 Refactored scope notification support: ScopeableFactory was removed, and the
** registration is now specific to each type of scopeable. For each case, the block
** will be registered for scope support (for that particular scopeable) only when
** the scopeable is 'active' (i.e. unnamed streams or accumulators are used). This
** allows a lazy registration of scopeables, to avoid the unnecessary overhead of
** processing all the scopeables for each and every block.
** CA 20221010 Fixed problems in CA/20220930:
** - for typed FUNCTION calls, convert the returned value to the expected type.
** - fixed caching issues when the call does not complete due to errors or for
** recursive calls.
** TJD 20220504 Java 11 compatibility minor changes
** CA 20221117 When resolving a SUPER procedure or function, if the resolution ends up in the
** SOURCE-PROCEDURE or one of the already processed super-procedure, then ignore
** it and move to another super-procedure.
** TW 20221123 Augmented the error feedback, which omitted the target invocation name if
** no parameters expected. Troubleshooting went haywire after that, since
** root causes were sought at the wrong frame in the stack (refs: #6906).
** CA 20220520 The constructor (static or instance) and destructor lookup now relies on the
** LegacySignature annotation to resolve them, instead of the Java method name -
** this is required for ctor overload support at conversion and runtime.
** CA 20220524 Fixed destructor execution.
** CA 20220601 Any TableParameter or DataSetParameter instances at a legacy OO method or
** constructor call must be transformed to their mode'ed version, depending on the
** INPUT, OUTPUT or INPUT-OUTPUT mode, and the DATASET-HANDLE, DATASET, TABLE-HANDLE
** or TABLE versions.
** CA 20220606 Added lazy loading of legacy converted classes.
** Fixed invokeFailure, must raise a STOP condition, and not a legacy exception.
** CA 20220727 Fixed OO dataset/table parameters (at the method definition and method call) when
** there is an APPEND option.
** CA 20221010 Fixed problems in CA/20220930:
** - for typed FUNCTION calls, convert the returned value to the expected type.
** - fixed caching issues when the call does not complete due to errors or for
** recursive calls.
** SVL 20230113 Improved performance by replacing some "for-each" loops with indexed "for" loops.
** HC 20230125 Added caching of resolved procedure internal entries.
** 084 CA 20230215 Added unknown.UNKNOWN singleton (used by FWD runtime at this time).
** 085 GBB 20230315 Redundant ErrorManager.buildErrorText removed before ErrorManager.displayError.
** 086 GBB 20230502 Run external procedure to produce error always if missing.
** 087 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 088 CA 20230615 Small optimization - avoid using obj.ref() and use instead obj.ref, if the 'obj'
** instance is known to be valid.
** 089 ME 20230905 Make invokeLegacyMethod public and add public resolveLegacyEntry (used by
** reflection).
** ME 20230919 Try to avoid index out of bounds exceptions on prepareParameters if more
** arguments used.
** 090 CA 20230928 The openedge method resolution when the match is done via a poly argument
** (like h::f1), it will assume the return type of the last found overload - the
** runtime will still resolve the target based on the arguments, but the lookup must
** be done on the type resolved by the conversion, and not the runtime time of the
** lvalue on the chain call. For this reason, and to allow the chained call to fail
** properly at runtime, both the lookup type and the return type of the call are
** emitted at the 'invokePoly' API call.
** CA 20231009 private methods can be invoked only if they are defined in the the current
** executing type.
** 091 CA 20231019 Proxy BDT instances must have the 'val' resolved before looking the target for
** dynamic calls.
** 092 GBB 20231023 SecurityManager context methods calls updated.
** 093 CA 20231023 Fixed 'resolveLegacyEntry' for 'unknown' type arguments.
** 094 CA 20231031 The constructor and destructor 'internal entry name' is the exact legacy name
** for this entry, as it is reported by PROGRAM-NAME and error callstack.
** 095 CA 20231030 Fixed dynamic invoke via legacy interface types.
** 096 CA 20231208 POLY OO method invocations must use the var's declared type and not the runtime
** type.
** 097 CA 20231213 The synthetic 'execute' methods emitted for legacy classes can be dropped if there
** is nothing emitted in them. These Java methods are also marked with Type.EXECUTE
** LegacySignature annotation, and their BlockManager API removed - the FWD runtime
** will run this only once.
** 098 SVL 20240207 Preserve case sensitivity of the returned character value of a dynamically called
** function.
** 099 CA 20240331 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime, so
** logical type checks must include sub-classes, too.
** 100 SC 20240508 Added method argument for checking if the function call is dynamic
** 101 GBB 20240610 StopConditionException from remote procedure invokation on appservers to be
** handled by the BlockManager.
** 102 GBB 20240105 Fixes QUIT executed on an appserver terminates the client.
** 103 SP 20240610 Fixed prepareArgs(modes, args) to set the correct mode'ed version for TABLE-HANDLE.
** 104 ES 20240715 Added public cleanupPending method.
** 105 AL2 20240712 Improved checking extent types (non-extent argument, extent parameter).
** 106 DDF 20240311 Modified initializeLegacyObject() method to avoid executing the constructor
** method based on an additional parameter passed.
** 107 SP 20240729 Added a case when we shouldn't cache the failed result for
** fuzzy parameter matching.
** 108 GBB 20240923 Expose error code number for procedure not found.
** 109 CA 20241030 Added immutable character constant support.
** 110 CA 20241105 Fixed resolution of native/external procedure calls which are resolved from
** super-procedures.
** 111 CA 20241211 RUN ... PERSISTENT ON SERVER hs SET hp1. must treat 'hp1' (which is actually the
** port-handle if we follow the syntax of RUN statement) as the persistent handle
** only if 'hs' is not the SESSION handle.
** 112 DDF 20250131 When a buffer is passed as a parameter, create a proxy from the expected type
** to the argument's instance, but only if the two dmos match.
** DDF 20250210 Added an additional parameter to isSchemaMatch() call.
** 113 GBB 20250403 Support child connections. Make some methods public to be accessed from the
** appserver package.
** 114 AS 20250408 Wrap the argument in a TableHandle in prepareArgs only if the handle is null.
** 115 PBB 20250410 Fixed bug with method resolution where a method receiving an object parameter
** would not be able to match on the exact signature
** 116 ES 20240221 Switch to throwing the StopConditionException through ErrorManager.
** ES 20250304 Raise a StopError legacy exception in case a procedure name is not found.
** 117 PBB 20250509 Improved the process of method resolution when parameters are extents.
** 118 PBB 20250522 Avoid the wrongful failure of the process of method resolution when the
** method is declared as taking a dynamic extent.
** Fixed the problem related to the java.lang.NoSuchMethodException error being
** printed at the server stdout when trying to cast an array to a BDT.
*/
/*
** 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.lang.ref.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.logging.*;
import com.esotericsoftware.reflectasm.*;
import com.goldencode.cache.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.library.*;
import com.goldencode.p2j.main.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.orm.DmoMetadataManager;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.util.BlockManager.TransactionType;
import com.goldencode.p2j.util.InvocationRequestPayload.InvokeType;
import com.goldencode.p2j.util.logging.*;
/**
* Progress compatible control-flow related static utility methods. Current
* implementation contains support for the:
* <p>
* <pre>
* Progress 4GL Feature Java Implementation
* ----------------------------------- ------------------------------------
* RUN [ <procedure> | VALUE() ] invoke()
* RUN ... IN <handle> invokeIn()
* RUN ... PERSIST invokePersistent()
* RUN ... PERSIST SET <handle> invokePersistentSet()
* RETURN-VALUE "global variable" getReturnValue()/setReturnValue()
* RUN SUPER runSuper()
* simple function invocation invokeFunction()
* FUNCTION ... IN handle invokeFunctionIn()
* DYNAMIC-FUNCTION invokeDynamicFunction()
* DYNAMIC-FUNCTION(... IN) invokeDynamicFunctionIn()
* SUPER() runSuper()
* RELEASE EXTERNAL [PROCEDURE] release()
* </pre>
*/
public class ControlFlowOps
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(ControlFlowOps.class);
/** Enum with the list of possible argument validation errors. */
public enum ArgValidationErrors
{
NO_ERROR,
INCORRECT_COUNT,
UNEXPECTED_PARAM,
INCORRECT_TYPES,
DYN_ARRAY_FOR_NON_ARRAY,
NON_ARRAY_FOR_DYN_ARRAY,
ARRAY_FOR_NON_ARRAY,
NON_ARRAY_FOR_ARRAY,
BAD_ARRAY_DIMENSION,
DEFER_VALIDATION
}
/** Exit code for procedure not found. */
public static final int EXIT_CODE_PROCEDURE_NOT_FOUND = 293;
/** Stores context-local state variables. */
private static ContextContainer work = new ContextContainer();
/**
* Get an instance of {@link ControlFlowOpsHelper}, which has the context-local instance saved,
* so API access can be done without a context-local query.
*
* @return See above.
*/
public static ControlFlowOpsHelper getHelper()
{
return new ControlFlowOpsHelper(work.get());
}
/**
* Attempts to unlpad the specified library from memory (the RELEASE EXTERNAL statement).
* This code just delegates the processing to the {@link NativeInvoker#release} method.
*
* @param libname
* The name of the library to attempt to unload.
*/
public static void release(character libname)
{
if (libname == null)
{
// quick/silent out, there is no way to unload a library in this case
return;
}
if (libname.isUnknown())
{
WorkArea wa = work.obtain();
// strange case where the error is raised in normal mode (without any message
// display) and in silent mode only the error flag is set (but normal details
// are not recorded)
wa.em.noRecordOrThrowError("Unknown cannot be given for a libname!");
}
else
{
release(libname.toStringMessage());
}
}
/**
* Attempts to unload the specified library from memory (the RELEASE EXTERNAL statement).
* This code just delegates the processing to the {@link NativeInvoker#release} method.
*
* @param libname
* The name of the library to attempt to unload. If <code>null</code> or the
* empty string are passed, this is a no-operation.
*/
public static void release(String libname)
{
if (libname == null || libname.length() == 0)
{
// quick/silent out, there is no way to unload a library in this case
return;
}
NativeInvoker.release(libname);
}
/**
* Gets the most recent RETURN-VALUE for the current user's session.
*
* @return The RETURN-VALUE which defaults to the empty string if a
* the equivalent of a Progress 4GL RUN statement has executed
* but no explicit RETURN-VALUE was set.
*/
public static character getReturnValue()
{
return work.obtain().returnValue;
}
/**
* Returns <code>true</code> if <code>RETURN-VALUE</code> was set during the call of the current root
* external program. Used to determine if we need to update <code>RETURN-VALUE</code> after an appserver
* call.
*
* @return <code>true</code> if <code>RETURN-VALUE</code> was set during the call of the current root
* external program.
*/
public static boolean isReturnValueSetInRootScope()
{
return work.obtain().returnValueSetInRootScope;
}
/**
* Reset the {@link WorkArea#returnValueSetInRootScope} flag.
*/
public static void resetReturnValueSetInRootScope()
{
work.obtain().returnValueSetInRootScope = false;
}
/**
* Sets the most recent RETURN-VALUE for the current user's session.
*
* @param returnValue
* The RETURN-VALUE to set or <code>null</code> to assign the
* empty string.
*/
public static void setReturnValue(character returnValue)
{
WorkArea wa = work.obtain();
if (returnValue == null)
{
wa.returnValue.assign(character.EMPTY_STRING);
}
else
{
wa.returnValue.assign(returnValue);
}
wa.returnValueSetInRootScope = wa.tm.isRootExternal();
}
/**
* Sets the most recent RETURN-VALUE for the current user's session.
*
* @param returnValue
* The RETURN-VALUE to set or <code>null</code> to assign the
* empty string.
*/
public static void setReturnValue(String returnValue)
{
WorkArea wa = work.obtain();
wa.returnValue.assign(returnValue == null ? "" : returnValue);
wa.returnValueSetInRootScope = wa.tm.isRootExternal();
}
/**
* Execute a SUPER() function call, forcing the returned value to be of the specified type.
*
* @param type
* The caller function's return type.
* @param iename
* The internal-entry's legacy 4GL name.
* @param args
* The argument list.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default, on error or for
* procedures.
*/
public static <T extends BaseDataType> T runSuper(Class<T> type, String iename, Object... args)
{
BaseDataType superVal = runSuper(iename, args);
T ret = null;
if (type.isAssignableFrom(superVal.getClass()))
{
ret = (T) superVal;
}
else
{
ret = (T) BaseDataType.generateUnknown(type);
ret.assign(superVal);
}
return ret;
}
/**
* Execute the RUN SUPER statement or SUPER function. Runtime decides on
* whether this is a function or procedure, based on the
* {@link TransactionManager#nearestExecutingBlock() nearest block}.
*
* @param iename
* The internal-entry's legacy 4GL name.
* @param args
* The argument list.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default, on error or for
* procedures.
*/
public static BaseDataType runSuper(String iename, Object... args)
{
WorkArea wa = work.obtain();
BlockDefinition block = wa.tm.nearestExecutingBlock();
boolean function = (block.type == BlockType.FUNCTION);
handle target = wa.pm.getSuperHandle(iename, function);
if (target == null)
{
return unknown.UNKNOWN;
}
character name = new character(iename);
if (function)
{
return invokeFunctionImpl(name, target, false, true, null, args);
}
else
{
invokeInImpl(name, target, true, null, args);
return unknown.UNKNOWN;
}
}
/**
* Execute a <code>RUN ... SINGLE-RUN ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingleRun(String name,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(true, new character(name), null, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLE-RUN SET h</code> statement.
*
* @param name
* The external program name.
*/
public static void invokeSingleRun(String name)
{
invokeSingletonOrSingleRun(true, new character(name), null, null, false);
}
/**
* Execute a <code>RUN ... SINGLETON ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingleton(String name,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(false, new character(name), null, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLETON</code> statement.
*
* @param name
* The external program name.
*/
public static void invokeSingleton(String name)
{
invokeSingletonOrSingleRun(false, new character(name), null, null, false);
}
/**
* Execute a <code>RUN ... SINGLE-RUN SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingleRunSet(String name,
handle hproc,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(true, new character(name), hproc, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLE-RUN SET h</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
*/
public static void invokeSingleRunSet(String name, handle hproc)
{
invokeSingletonOrSingleRun(true, new character(name), hproc, null, false);
}
/**
* Execute a <code>RUN ... SINGLETON SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingletonSet(String name,
handle hproc,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(false, new character(name), hproc, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLETON SET h</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
*/
public static void invokeSingletonSet(String name, handle hproc)
{
invokeSingletonOrSingleRun(false, new character(name), hproc, null, false);
}
/**
* Execute a <code>RUN ... SINGLETON ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingleRun(character name,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(true, name, null, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLE-RUN</code> statement.
*
* @param name
* The external program name.
*/
public static void invokeSingleRun(character name)
{
invokeSingletonOrSingleRun(true, name, null, null, false);
}
/**
* Execute a <code>RUN ... SINGLETON ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingleton(character name,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(false, name, null, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLETON.</code> statement.
*
* @param name
* The external program name.
*/
public static void invokeSingleton(character name)
{
invokeSingletonOrSingleRun(false, name, null, null, false);
}
/**
* Execute a <code>RUN ... SINGLE-RUN SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingleRunSet(character name,
handle hproc,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(true, name, hproc, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLE-RUN SET h</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
*/
public static void invokeSingleRunSet(character name, handle hproc)
{
invokeSingletonOrSingleRun(true, name, hproc, null, false);
}
/**
* Execute a <code>RUN ... SINGLETON SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
* @param remoteHandle
* The remote server.
* @param transactionDistinct
* Flag indicating if TRANSACTION-DISTINCT was set.
*/
public static void invokeRemoteSingletonSet(character name,
handle hproc,
handle remoteHandle,
boolean transactionDistinct)
{
invokeSingletonOrSingleRun(false, name, hproc, remoteHandle, transactionDistinct);
}
/**
* Execute a <code>RUN ... SINGLETON SET h</code> statement.
*
* @param name
* The external program name.
* @param hproc
* The handle where to save the deferred configuration.
*/
public static void invokeSingletonSet(character name, handle hproc)
{
invokeSingletonOrSingleRun(false, name, hproc, null, false);
}
/**
* Invalidate the entire {@link WorkArea#callSiteCache cache}.
*/
public static void invalidateCallSiteCache()
{
WorkArea wa = work.obtain();
wa.callSiteCache.clear();
}
/**
* Invalidate the {@link WorkArea#callSiteCache cache} for the specified instance.
*
* @param referent
* The instance to remove.
*/
public static void invalidateCallSiteCache(Object referent)
{
WorkArea wa = work.obtain();
wa.callSiteCache.remove(referent);
}
/**
* Using the current invoke configuration, perform the call.
*
* @param cfg
* The configuration to invoke.
*
* @return The return value from the block (or its nested blocks) which is the
* <code>unknown value</code> by default, on error or for procedures.
*/
public static BaseDataType invoke(InvokeConfig cfg)
{
return invoke(null, null, cfg);
}
/**
* Using the current invoke configuration, perform the call.
*
* @param cls
* The return type in case of function calls.
* @param resource
* When not-null, this is an FWD internal call, associated with a callback for a resource.
* @param cfg
* The configuration to invoke.
*
* @return The return value from the block (or its nested blocks) which is the
* <code>unknown value</code> by default, on error or for procedures.
*/
public static BaseDataType invoke(Class cls, WrappedResource resource, InvokeConfig cfg)
{
WorkArea wa = work.obtain();
boolean cachedExternalProgram = false;
if (cfg.getCallSite() != null &&
!cfg.isAsynchronous() &&
!cfg.isAsThread() &&
!cfg.isSingleRun() &&
!cfg.isSingleton() &&
!cfg.isTransactionDistinct() &&
!cfg.isClass() &&
!(cfg.getInHandle() != null && cfg.getInHandle().get() instanceof DeferredProgram))
{
// SINGLE-RUN and SINGLETON are not being cached. this is because the returned handle is always
// a deferred program. the same for "IN handle" targeting a DeferredProgram
// AS-THREAD is not being cached, as the resolution is done on a separate thread.
// TRANSACTION DISTINCT is always for remote calls - if ON SERVER SESSION is specified, it will fail
// ASYNC or ON SERVER are not being cached
// TODO: OO calls are not supported in conversion yet
Map<InvokeConfig, LastInvokeDetails> thisCache = null;
Object thisCaller = (resource == null ? wa.pm._thisProcedure() : resource);
if (cfg.isPersistent())
{
// look only in the external program cache
thisCache = wa.extProgcallSiteCache;
}
else
{
// first check the this-procedure cache, and after that check again the external program cache.
thisCache = wa.callSiteCache.get(thisCaller);
}
LastInvokeDetails lastInvoke = thisCache == null ? null : thisCache.get(cfg.getCallSite());
if (lastInvoke == null && !cfg.isPersistent())
{
// check external programs again
thisCache = wa.extProgcallSiteCache;
lastInvoke = thisCache.get(cfg.getCallSite());
}
cachedExternalProgram = lastInvoke != null &&
lastInvoke.isExternalProgram() &&
lastInvoke.matchesRun(cfg);
if (cachedExternalProgram)
{
// for an external program, the method can't be used - we cache only the target class name and
// for to run this as an external program (persistent or not)
wa.externalResolver.className = (String) lastInvoke.reference.get();
}
else if (lastInvoke != null && (lastInvoke.matchesRun(cfg) || lastInvoke.matchesFunction(cfg)))
{
// use the cache for RUN statement
Object reference = lastInvoke.reference.get(); // reference can never be null for RUN, only for OO
// only for internal entries for now
Object[] args = cfg.getArguments();
InternalEntryCaller caller = new InternalEntryCaller(wa);
caller.callerInstance = reference;
caller.wa = wa;
caller.mthd = lastInvoke.method;
caller.methodName = lastInvoke.method.getName();
caller.ie = null;
caller.iename = cfg.getTarget();
ArgValidationErrors argValid = ArgValidationErrors.NO_ERROR;
if (args != null && args.length > 0)
{
// pass a copy of the 'args' array, otherwise the non-cached call will use the modified args
Object[] copy = Arrays.copyOf(args, args.length);
argValid = InternalEntryCaller.valid(caller, cfg.isFunction(), cfg.getModes(), new int[2], copy);
if (argValid == ArgValidationErrors.NO_ERROR)
{
args = copy;
}
}
if (argValid == ArgValidationErrors.NO_ERROR)
{
Exception deferred = null;
Class<?> def = reference.getClass();
boolean superCall = cfg.isSuperCall();
Object target = cfg.getInHandle() == null ? wa.pm._thisProcedure() : cfg.getInHandle().get();
String iename = cfg.getTarget();
String pname = null;
String legacyName = lastInvoke.legacyName;
try
{
wa.pm.pushCalleeInfo(def,
superCall,
target,
reference,
iename,
pname,
cfg.isFunction(),
cfg.isPersistent(),
cfg.isSingleton(),
cfg.isSingleRun());
// set the legacy name as it was resolved.
wa.pm.peekCalleeInfo().setLegacyName(legacyName);
// this is NOT a pure Java call, avoid the BlockManager.checkJavaCall overhead
wa.bm.markPendingDynCall();
try
{
BaseDataType res = (BaseDataType) lastInvoke.method.invoke(reference, args);
res = checkInvokeResult(res, iename, cfg.isFunction(), cfg.isDynamicFunction());
if (cls != null && cfg.isFunction())
{
res = convertValue(cls, res);
}
return res;
}
finally
{
wa.pm.popCalleeInfo();
}
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
throw ce;
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
// all other cases
catch (Exception exp)
{
deferred = exp;
}
if (deferred != null)
{
invokeError(caller, iename, deferred);
}
}
}
else if (lastInvoke != null)
{
thisCache.remove(cfg.getCallSite());
}
// otherwise, if cached value can't be used, let it execute and potentially cache
wa.currentInvoke = cfg;
wa.currentCaller = thisCaller;
}
try
{
if (cfg.isAsynchronous() && cfg.getAsyncHandle() == null)
{
cfg.setAsyncHandle(new handle());
}
if (cfg.isPersistent() && cfg.getProcedureHandle() == null)
{
boolean validPortHandle = cfg.getPortHandle() != null &&
cfg.getServerHandle() != null &&
cfg.getServerHandle()._isValid();
if (validPortHandle && cfg.getServerHandle().get() instanceof ServerImpl)
{
// port-handle is 'morhped' into the procedure handle only if something is ran persistent and
// remote
cfg.setProcedureHandle(cfg.getPortHandle());
cfg.setPortHandle(null);
}
else
{
// otherwise, ensure no port-handle is set if we are running something on SESSION
if (validPortHandle && cfg.getServerHandle().get() instanceof CommonSession)
{
cfg.setPortHandle(null);
}
// ensure we have a temp handle here
cfg.setProcedureHandle(new handle());
}
}
// TODO: flatten the stack depth - compute here the final ControlFlow API call for each case
if (cfg.isSuperCall())
{
String target = wa.pm.peekCalleeInfo().getInternalEntryName();
if (cls == null)
{
return ControlFlowOps.runSuper(target, cfg.getArguments());
}
else
{
return ControlFlowOps.runSuper(cls, target, cfg.getArguments());
}
}
else if (cfg.isFunction())
{
if (cls == null)
{
if (cfg.isDynamicFunction())
{
if (cfg.getInHandle() == null)
{
return ControlFlowOps.invokeDynamicFunctionWithMode(cfg.getTarget(),
cfg.getModes(),
cfg.getArguments());
}
else
{
return ControlFlowOps.invokeDynamicFunctionInWithMode(cfg.getTarget(),
cfg.getInHandle(),
cfg.getModes(),
cfg.getArguments());
}
}
else
{
if (cfg.getInHandle() == null)
{
return ControlFlowOps.invokeFunctionWithMode(cfg.getTarget(),
cfg.getModes(),
cfg.getArguments());
}
else
{
return ControlFlowOps.invokeFunctionInWithMode(cfg.getTarget(),
cfg.getInHandle(),
cfg.getModes(),
cfg.getArguments());
}
}
}
else
{
if (cfg.isDynamicFunction())
{
if (cfg.getInHandle() == null)
{
return ControlFlowOps.invokeDynamicFunctionWithMode(cls,
cfg.getTarget(),
cfg.getModes(),
cfg.getArguments());
}
else
{
return ControlFlowOps.invokeDynamicFunctionInWithMode(cls,
cfg.getTarget(),
cfg.getInHandle(),
cfg.getModes(),
cfg.getArguments());
}
}
else
{
if (cfg.getInHandle() == null)
{
return ControlFlowOps.invokeFunctionWithMode(cls,
cfg.getTarget(),
cfg.getModes(),
cfg.getArguments());
}
else
{
return ControlFlowOps.invokeFunctionInWithMode(cls,
cfg.getTarget(),
cfg.getInHandle(),
cfg.getModes(),
cfg.getArguments());
}
}
}
}
else if (cfg.isAsThread())
{
if (cfg.getProcedureHandle() != null)
{
ControlFlowOps.invokeAsThreadWithMode(cfg.getTarget(),
cfg.getModes(),
cfg.getArguments());
}
else
{
ControlFlowOps.invokeAsThreadSetWithMode(cfg.getTarget(),
cfg.getProcedureHandle(),
cfg.getModes(),
cfg.getArguments());
}
return unknown.UNKNOWN;
}
else
{
if (cfg.getServerHandle() != null)
{
if (cfg.isAsynchronous())
{
if (cfg.isPersistent())
{
ControlFlowOps.invokeRemotePersistentSetAsyncWithMode(cfg.getTarget(),
cfg.getProcedureHandle(),
cfg.getPortHandle(),
cfg.getServerHandle(),
cfg.isTransactionDistinct(),
cfg.getAsyncHandle(),
cfg.getEventProcedure(),
cfg.getEventProcedureContext(),
cfg.getModes(),
cfg.getArguments());
}
else
{
ControlFlowOps.invokeRemoteAsyncWithMode(cfg.getTarget(),
cfg.getPortHandle(),
cfg.getServerHandle(),
cfg.isTransactionDistinct(),
cfg.getAsyncHandle(),
cfg.getEventProcedure(),
cfg.getEventProcedureContext(),
cfg.getModes(),
cfg.getArguments());
}
}
else
{
if (cfg.isPersistent())
{
ControlFlowOps.invokeRemotePersistentSetWithMode(cfg.getTarget(),
cfg.getProcedureHandle(),
cfg.getPortHandle(),
cfg.getServerHandle(),
cfg.isTransactionDistinct(),
cfg.getModes(),
cfg.getArguments());
}
else if (cfg.isSingleRun())
{
ControlFlowOps.invokeRemoteSingleRunSet(cfg.getTarget(),
cfg.getProcedureHandle(),
cfg.getServerHandle(),
cfg.isTransactionDistinct());
}
else if (cfg.isSingleton())
{
ControlFlowOps.invokeRemoteSingletonSet(cfg.getTarget(),
cfg.getProcedureHandle(),
cfg.getServerHandle(),
cfg.isTransactionDistinct());
}
else
{
ControlFlowOps.invokeRemoteWithMode(cfg.getTarget(),
cfg.getPortHandle(),
cfg.getServerHandle(),
cfg.isTransactionDistinct(),
cfg.getModes(),
cfg.getArguments());
}
}
}
else if (cfg.isPersistent())
{
// this will automatically use any resolved and cached class name
ControlFlowOps.invokePersistentSetWithMode(cfg.getTarget(),
cfg.getProcedureHandle(),
cfg.getModes(),
cfg.getArguments());
}
else if (cfg.isSingleRun())
{
// not cached
ControlFlowOps.invokeSingleRunSet(cfg.getTarget(), cfg.getProcedureHandle());
}
else if (cfg.isSingleton())
{
// not cached
ControlFlowOps.invokeSingletonSet(cfg.getTarget(), cfg.getProcedureHandle());
}
else if (cfg.getInHandle() == null)
{
if (cachedExternalProgram || TransactionManager.isGlobalBlock())
{
// force an external program invocation
ControlFlowOps.invokeExternalProcedure(new character(cfg.getTarget()),
false, null, false, null,
cfg.getModes(), cfg.getArguments());
}
else
{
ControlFlowOps.invokeWithMode(cfg.getTarget(), cfg.getModes(), cfg.getArguments());
}
}
else
{
if (cfg.isAsynchronous())
{
// not cached
ControlFlowOps.invokeInAsyncWithMode(cfg.getTarget(),
cfg.getInHandle(),
cfg.getAsyncHandle(),
cfg.getEventProcedure(),
cfg.getEventProcedureContext(),
cfg.getModes(),
cfg.getArguments());
}
else
{
// internal procedure
ControlFlowOps.invokeInWithMode(cfg.getTarget(),
cfg.getInHandle(),
cfg.getModes(),
cfg.getArguments());
}
}
return unknown.UNKNOWN;
}
}
finally
{
// the call may have failed (argument validation, etc), reset these
wa.currentCaller = null;
wa.currentInvoke = null;
}
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeWithMode(String name,
String modes,
Object... args)
{
invokeWithMode(new character(name), modes, args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeWithMode(character name,
String modes,
Object... args)
{
invokeImpl((handle) null, null, name, false, false, false, false, modes, args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param args
* The procedure's arguments.
*/
public static void invoke(String name, Object... args)
{
invoke(new character(name), args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param args
* The procedure's arguments.
*/
public static void invoke(character name, Object... args)
{
invokeImpl((handle) null, null, name, false, false, false, false, null, args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteWithMode(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
invokeRemoteWithMode(new character(name), portHandle,
remoteHandle, transactionDistinct,
modes, args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteWithMode(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
invokeRemoteWithModeImpl(null, name,
portHandle, remoteHandle,
transactionDistinct,
modes, args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN statement).
*
* @param asyncReq
* When not-null, an {@link AsyncRequestImpl} instance for the associated async
* request.
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
private static void invokeRemoteWithModeImpl(AsyncRequestImpl asyncReq,
character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
if (portHandle != null)
{
// this is a RUN ... SET ... ON SERVER webService
invokePersistentImpl(asyncReq, remoteHandle, name, null, portHandle,
transactionDistinct, modes, args);
}
else
{
// this is a RUN ... ON SERVER appServer
invokeImpl(asyncReq, remoteHandle, null, name,
false, false, false,
transactionDistinct, modes, args);
}
}
/**
* Invoke the procedure with the given name on a remote server (emitted for a standard RUN ...
* ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param args
* The procedure's arguments.
*/
public static void invokeRemote(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
Object... args)
{
invokeRemote(new character(name), portHandle, remoteHandle, transactionDistinct, args);
}
/**
* Invoke the procedure with the given name on a remote server (emitted for a standard RUN ...
* ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param args
* The procedure's arguments.
*/
public static void invokeRemote(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
Object... args)
{
invokeRemoteWithMode(new character(name), portHandle,
remoteHandle, transactionDistinct,
null, args);
}
/**
* Invoke the procedure with the given name on a remote server (emitted for a standard RUN ...
* ON ... ASYNC SET ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsyncWithMode(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemoteAsyncWithMode(new character(name), portHandle,
remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName), inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsyncWithMode(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemoteAsyncWithMode(name, portHandle, remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName), inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsyncWithMode(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemoteAsyncWithMode(new character(name), portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsyncWithMode(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
AsyncCall remoteRequest = new AsyncCall(remoteHandle, portHandle, name, new handle(),
asyncHandle, eventProcName, inEventprocHandle,
transactionDistinct,
modes, args)
{
@Override
public void run()
{
// use a copy here, to not allow the user to change the reference
invokeRemoteWithModeImpl(asyncReq, pName,
portHandle, hServer,
transactionDistinct,
asyncReq.getModes(), asyncReq.getArgs());
}
};
invokeAsyncImpl(remoteRequest, remoteHandle, asyncHandle, new handle());
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsync(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemoteAsync(new character(name), portHandle, remoteHandle , transactionDistinct,
asyncHandle, new character(eventProcName), inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsync(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemoteAsync(name, portHandle, remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName), inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsync(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemoteAsync(new character(name), portHandle, remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemoteAsync(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemoteAsyncWithMode(name, portHandle, remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
null, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeInWithMode(String name,
handle h,
String modes,
Object... args)
{
invokeInImpl(new character(name), h, false, modes, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeInWithMode(character name,
handle h,
String modes,
Object... args)
{
invokeInImpl(name, h, false, modes, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param args
* The procedure's arguments.
*/
public static void invokeIn(String name, handle h, Object... args)
{
invokeInImpl(new character(name), h, false, null, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param args
* The procedure's arguments.
*/
public static void invokeIn(character name, handle h, Object... args)
{
invokeInImpl(name, h, false, null, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsyncWithMode(String name,
handle h,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeInAsyncWithMode(new character(name), h,
asyncHandle, new character(eventProcName), inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN ASYNC.. handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsyncWithMode(character name,
handle h,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeInAsyncWithMode(name, h,
asyncHandle, new character(eventProcName), inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN ASYNC.. handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsyncWithMode(String name,
handle h,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeInAsyncWithMode(new character(name), h, asyncHandle, eventProcName, inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsyncWithMode(character name,
handle h,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
ServerImpl server = null;
if (h != null && h._isValid())
{
WrappedResource res = h.getResource();
if (res instanceof PersistentProcedure)
{
handle hserver = ((PersistentProcedure) res).getServerHandle();
if (!hserver.isUnknown())
{
server = (ServerImpl) hserver.getResource();
}
}
}
// if no server, default to SESSION
handle hServer = (server != null ? new handle(server) : SessionUtils.asHandle());
AsyncCall remoteRequest = new AsyncCall(hServer, null, name, h,
asyncHandle, eventProcName, inEventprocHandle,
false,
modes, args)
{
@Override
public void run()
{
// use a copy here, to not allow the user to change the reference
invokeImpl(asyncReq, (handle) null, pHandle, pName,
false, false, false, false,
asyncReq.getModes(), asyncReq.getArgs());
}
};
invokeAsyncImpl(remoteRequest, hServer, asyncHandle, h);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsync(String name,
handle h,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeInAsync(new character(name), h,
asyncHandle, new character(eventProcName), inEventprocHandle, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle ASYNC.. statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsync(character name,
handle h,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeInAsync(name, h, asyncHandle, new character(eventProcName), inEventprocHandle, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... IN handle ASYNC.. statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsync(String name,
handle h,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeInAsync(new character(name), h, asyncHandle, eventProcName, inEventprocHandle, args);
}
/**
* Invoke the procedure with the given name, defined in the given handle
* (emitted for a standard RUN ... handle IN statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which this procedure belongs. When
* <code>null</code>, defaults to THIS-PROCEDURE.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeInAsync(character name,
handle h,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeInAsyncWithMode(name, h,
asyncHandle, eventProcName, inEventprocHandle,
null, args);
}
/**
* Invoke the procedure with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokePersistentWithMode(String name,
String modes,
Object... args)
{
invokePersistentWithMode(new character(name), modes, args);
}
/**
* Invoke the procedure with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokePersistentWithMode(character name,
String modes,
Object... args)
{
invokePersistentImpl(null, name, null, null, false, modes, args);
}
/**
* Invoke the procedure with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param args
* The procedure's arguments.
*/
public static void invokePersistent(String name, Object... args)
{
invokePersistent(new character(name), args);
}
/**
* Invoke the procedure with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param args
* The procedure's arguments.
*/
public static void invokePersistent(character name, Object... args)
{
invokePersistentImpl(null, name, null, null, false, null, args);
}
/**
* Invoke the procedure with the given name, and save its procedure handle
* in the given handle (emitted for a RUN ... PERSISTENT set handle
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which the procedure reference must be saved.
* When <code>null</code>, just adds the procedure to the
* persistent procedure list.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokePersistentSetWithMode(String name,
handle h,
String modes,
Object... args)
{
invokePersistentSetWithMode(new character(name), h, modes, args);
}
/**
* Invoke the procedure with the given name, and save its procedure handle
* in the given handle (emitted for a RUN ... PERSISTENT set handle
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which the procedure reference must be saved.
* When <code>null</code>, just adds the procedure to the
* persistent procedure list.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokePersistentSetWithMode(character name,
handle h,
String modes,
Object... args)
{
invokePersistentImpl(null, name, h, null, false, modes, args);
}
/**
* Invoke the procedure with the given name, and save its procedure handle
* in the given handle (emitted for a RUN ... PERSISTENT set handle
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which the procedure reference must be saved.
* When <code>null</code>, just adds the procedure to the
* persistent procedure list.
* @param args
* The procedure's arguments.
*/
public static void invokePersistentSet(String name,
handle h,
Object... args)
{
invokePersistentSet(new character(name), h, args);
}
/**
* Invoke the procedure with the given name, and save its procedure handle
* in the given handle (emitted for a RUN ... PERSISTENT set handle
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which the procedure reference must be saved.
* When <code>null</code>, just adds the procedure to the
* persistent procedure list.
* @param args
* The procedure's arguments.
*/
public static void invokePersistentSet(character name,
handle h,
Object... args)
{
invokePersistentImpl(null, name, h, null, false, null, args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetWithMode(String name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
invokeRemotePersistentSetWithMode(new character(name), persistHandle, portHandle,
remoteHandle, transactionDistinct,
modes, args);
}
/**
* Invoke the procedure with the given name (emitted for a standard RUN
* statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetWithMode(character name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
invokePersistentImpl(remoteHandle, name,
persistHandle, portHandle,
transactionDistinct, modes, args);
}
/**
* Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT SET ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSet(String name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
Object... args)
{
invokeRemotePersistentSet(new character(name), persistHandle, portHandle,
remoteHandle, transactionDistinct,
args);
}
/**
* Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT SET ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSet(character name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
Object... args)
{
invokeRemotePersistentSetWithMode(name, persistHandle, portHandle,
remoteHandle, transactionDistinct,
null, args);
}
/**
* Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentWithMode(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
invokeRemotePersistentWithMode(new character(name), portHandle,
remoteHandle, transactionDistinct,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentWithMode(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
invokeRemotePersistentSetWithMode(name, null, portHandle,
remoteHandle, transactionDistinct,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistent(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
Object... args)
{
invokeRemotePersistent(new character(name), portHandle,
remoteHandle, transactionDistinct,
args);
}
/**
* Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
* (emitted for a RUN ... PERSISTENT ON ... statement).
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistent(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
Object... args)
{
invokeRemotePersistentWithMode(name, portHandle,
remoteHandle, transactionDistinct,
null, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsyncWithMode(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemotePersistentAsyncWithMode(new character(name), portHandle,
remoteHandle, transactionDistinct, asyncHandle,
new character(eventProcName), inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsyncWithMode(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemotePersistentAsyncWithMode(name, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName),
inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsyncWithMode(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemotePersistentAsyncWithMode(new character(name), portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsyncWithMode(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemotePersistentSetAsyncWithMode(name, null, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsync(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentAsync(new character(name), portHandle,
remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName), inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsync(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentAsync(name, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName), inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsync(String name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentAsync(new character(name), portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentAsync(character name,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentSetAsync(name, null, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsyncWithMode(String name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemotePersistentSetAsyncWithMode(new character(name), persistHandle, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName),
inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsyncWithMode(character name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemotePersistentSetAsyncWithMode(name, persistHandle, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName),
inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsyncWithMode(String name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
invokeRemotePersistentSetAsyncWithMode(new character(name), persistHandle, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
modes, args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsyncWithMode(character name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
String modes,
Object... args)
{
// validate server first
if (!validServer(remoteHandle, null, name))
{
return;
}
boolean isServer = (remoteHandle.getResource() instanceof ServerImpl);
ServerImpl server = null;
if (!isServer)
{
if (transactionDistinct)
{
invalidHandle(name);
return;
}
}
else
{
server = (ServerImpl) remoteHandle.getResource();
if (server.isWebService())
{
final String err =
"Unexpected Web service client error. Copy the following message and contact " +
"technical support. Web service procedure not initialized in cwsSend (11461)";
ErrorManager.recordOrThrowError(11461, err, false, true);
return;
}
}
// do this only if the server is still connected; if not, it will fallback to non-async
// processing to produce the appropriate error
final boolean obtainProxy = isServer &&
server._connected() &&
server.getServerHelper().isSessionFree();
AsyncCall remoteRequest = new AsyncCall(remoteHandle, portHandle, name, new handle(),
asyncHandle, eventProcName, inEventprocHandle,
transactionDistinct,
modes, args)
{
@Override
public void run()
{
if (obtainProxy)
{
// if truly-async request, must run the "execute" method on the resolved proxy
// if the proxy is invalid or can't map attributes or other error, then error
// messages are generated now
initializeProxy(this);
}
else
{
// if not a server, go the normal way
invokePersistentImpl(asyncReq, hServer, pName, pHandle, portHandle,
transactionDistinct,
asyncReq.getModes(), asyncReq.getArgs());
}
}
};
if (obtainProxy)
{
AppServerHelper helper = (AppServerHelper) server.getServerHelper();
ProxyProcedureWrapper proxy = helper.obtainProxy(name);
if (proxy.getChildConnectionId() != null)
{
remoteRequest.asyncReq.setConnectionId(proxy.getChildConnectionId());
}
persistHandle.assign(proxy);
remoteRequest.pHandle.assign(proxy);
}
invokeAsyncImpl(remoteRequest, remoteHandle, asyncHandle, new handle());
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT SET ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsync(String name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentSetAsync(new character(name), persistHandle, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, new character(eventProcName), inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT SET ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsync(character name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
String eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentSetAsync(name, persistHandle, portHandle,
remoteHandle, transactionDistinct, asyncHandle,
new character(eventProcName), inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ... ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsync(String name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentSetAsync(new character(name), persistHandle, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
args);
}
/**
* Invoke the procedure on a remote server with the given name
* (emitted for a standard RUN ...PERSISTENT SET ON ... ASYNC SET ...
*
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle in which it will put the persistent external procedure.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param remoteHandle
* The handle to the remote object used to run the procedure
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
* null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param args
* The procedure's arguments.
*/
public static void invokeRemotePersistentSetAsync(character name,
handle persistHandle,
handle portHandle,
handle remoteHandle,
boolean transactionDistinct,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
Object... args)
{
invokeRemotePersistentSetAsyncWithMode(name, persistHandle, portHandle,
remoteHandle, transactionDistinct,
asyncHandle, eventProcName, inEventprocHandle,
null, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunctionWithMode(Class<T> cls,
String name,
String modes,
Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), null, true, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunctionWithMode(Class<T> cls,
character name,
String modes,
Object... args)
{
BaseDataType res = invokeFunctionImpl(name, null, true, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunctionInWithMode(Class<T> cls,
String name,
handle h,
String modes,
Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), h, true, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunctionInWithMode(Class<T> cls,
character name,
handle h,
String modes,
Object... args)
{
BaseDataType res = invokeFunctionImpl(name, h, true, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunction(Class<T> cls,
String name,
Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), null, true, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunction(Class<T> cls,
character name,
Object... args)
{
BaseDataType res = invokeFunctionImpl(name, null, true, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunctionIn(Class<T> cls,
String name,
handle h,
Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), h, true, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeDynamicFunctionIn(Class<T> cls,
character name,
handle h,
Object... args)
{
BaseDataType res = invokeFunctionImpl(name, h, true, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunctionWithMode(String name,
String modes,
Object... args)
{
return invokeFunctionImpl(new character(name), null, true, false, modes, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunctionWithMode(character name,
String modes,
Object... args)
{
return invokeFunctionImpl(name, null, true, false, modes, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunctionInWithMode(String name,
handle h,
String modes,
Object... args)
{
return invokeFunctionImpl(new character(name), h, true, false, modes, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunctionInWithMode(character name,
handle h,
String modes,
Object... args)
{
return invokeFunctionImpl(name, h, true, false, modes, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunction(String name,
Object... args)
{
return invokeFunctionImpl(new character(name), null, true, false, null, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION statement.
*
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunction(character name,
Object... args)
{
return invokeFunctionImpl(name, null, true, false, null, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunctionIn(String name,
handle h,
Object... args)
{
return invokeFunctionImpl(new character(name), h, true, false, null, args);
}
/**
* Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeDynamicFunctionIn(character name,
handle h,
Object... args)
{
return invokeFunctionImpl(name, h, true, false, null, args);
}
/**
* Emitted for plain function calls.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeFunctionWithMode(Class<T> cls, String name, String modes, Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), null, false, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for plain function calls.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeFunctionWithMode(Class<T> cls, character name, String modes, Object... args)
{
BaseDataType res = invokeFunctionImpl(name, null, false, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeFunctionInWithMode(Class<T> cls, String name, handle h, String modes, Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), h, false, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T
invokeFunctionInWithMode(Class<T> cls, character name, handle h, String modes, Object... args)
{
BaseDataType res = invokeFunctionImpl(name, h, false, false, modes, args);
return convertValue(cls, res);
}
/**
* Emitted for plain function calls.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T invokeFunction(Class<T> cls,
String name,
Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), null, false, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for plain function calls.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T invokeFunction(Class<T> cls,
character name,
Object... args)
{
BaseDataType res = invokeFunctionImpl(name, null, false, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T invokeFunctionIn(Class<T> cls,
String name,
handle h,
Object... args)
{
BaseDataType res = invokeFunctionImpl(new character(name), h, false, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param cls
* Class.
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static <T extends BaseDataType> T invokeFunctionIn(Class<T> cls,
character name,
handle h,
Object... args)
{
BaseDataType res = invokeFunctionImpl(name, h, false, false, null, args);
return convertValue(cls, res);
}
/**
* Emitted for plain function calls.
*
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunctionWithMode(String name,
String modes,
Object... args)
{
return invokeFunctionImpl(new character(name), null, false, false, modes, args);
}
/**
* Emitted for plain function calls.
*
* @param name
* The legacy 4GL name for the function.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunctionWithMode(character name,
String modes,
Object... args)
{
return invokeFunctionImpl(name, null, false, false, modes, args);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunctionInWithMode(String name,
handle h,
String modes,
Object... args)
{
return invokeFunctionImpl(new character(name), h, false, false, modes, args);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunctionInWithMode(character name,
handle h,
String modes,
Object... args)
{
return invokeFunctionImpl(name, h, false, false, modes, args);
}
/**
* Emitted for plain function calls.
*
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunction(String name, Object... args)
{
return invokeFunctionImpl(new character(name), null, false, false, null, args);
}
/**
* Emitted for plain function calls.
*
* @param name
* The legacy 4GL name for the function.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunction(character name, Object... args)
{
return invokeFunctionImpl(name, null, false, false, null, args);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunctionIn(String name, handle h, Object... args)
{
return invokeFunctionImpl(new character(name), h, false, false, null, args);
}
/**
* Emitted for the plain function calls, when they are defined as
* FUNCTION ... IN handle.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
public static BaseDataType invokeFunctionIn(character name, handle h, Object... args)
{
return invokeFunctionImpl(name, h, false, false, null, args);
}
/**
* Invoke the procedure with the given name in a related thread.
* <p>
* This implements the RUN ... AS-THREAD SET ht. statement.
*
* @param name
* The legacy 4GL name for the procedure.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeAsThreadWithMode(String name, String modes, Object... args)
{
invokeAsThreadSetWithMode(name, null, modes, args);
}
/**
* Invoke the procedure with the given name in a related thread, and save the resource which
* can manage the related thread at the caller in the specified handle.
* <p>
* This implements the RUN ... AS-THREAD SET ht. statement.
*
* @param name
* The legacy 4GL name for the procedure.
* @param ht
* The handle where to save the resource to manage the related thread.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public static void invokeAsThreadSetWithMode(String name,
handle ht,
String modes,
Object... args)
{
invokeImpl(work.obtain().resolvers,
null,
new character(name),
false,
false,
false,
false,
true,
ht,
modes,
new ArrayArgumentResolver(args));
}
/**
* Invoke the procedure with the given name in a related thread.
* <p>
* This implements the RUN ... AS-THREAD. statement.
*
* @param name
* The legacy 4GL name for the procedure.
*/
public static void invokeAsThread(String name)
{
invokeAsThreadSetWithMode(name, null, null);
}
/**
* Invoke the procedure with the given name in a related thread, and save the resource which
* can manage the related thread at the caller in the specified handle.
* <p>
* This implements the RUN ... AS-THREAD SET ht. statement.
*
* @param name
* The legacy 4GL name for the procedure.
* @param ht
* The handle where to save the resource to manage the related thread.
*/
public static void invokeAsThreadSet(String name, handle ht)
{
invokeAsThreadSetWithMode(name, ht, null);
}
/**
* Check if an internal procedure or function is reachable and valid. This searches the
* specified handle and all the super-procedures.
*
* @param h
* A valid procedure handle.
* @param name
* The procedure or function name.
* @param function
* Flag indicating if the search is for a function or for an internal procedure.
* @param modes
* The parameter modes.
* @param validateModes
* Flag indicating if the parameter modes should be validated or not.
* @param args
* The passed arguments.
*
* @return null if the internal entry can't be found; otherwise ArgValidationErrors instance
* with error status or {@link ArgValidationErrors#NO_ERROR} if error is not exists.
*/
public static ArgValidationErrors reachableInternalEntry(handle h,
String name,
boolean function,
String modes,
boolean validateModes,
Object... args)
{
WorkArea wa = work.obtain();
InternalEntryCaller caller = null;
ArrayList<Resolver> inHandleResolvers = wa.inHandleResolvers;
for (int i = 0; i < inHandleResolvers.size(); i++)
{
Resolver r = inHandleResolvers.get(i);
caller = r.resolve(h, name, function, false, null);
if (caller != null)
{
break;
}
}
if (caller == null)
{
// was not found
return null;
}
ArgValidationErrors valid = caller.valid(function, modes, new int[2], args);
if (valid != ArgValidationErrors.NO_ERROR)
{
return valid;
}
// validate the parameter modes
String definedModes = caller.getParameterModes(function);
if (validateModes &&
(modes != null && !modes.equals(definedModes) ||
(modes == null && definedModes != null)))
{
return ArgValidationErrors.INCORRECT_TYPES;
}
return valid;
}
/**
* This is a special API which is used to delegate a call from converted 4GL code
* (via _P2J_REMOTE_CALL_ functions) to the remote side. Usually, this is used when P2J client
* runs in embedded mode, i.e. the P2J screens are embedded in another customer-specific
* application. The call will be remoted into the customer-specific application.
* <p>
* This API is expected to block waiting for a response.
*
* @param content
* The request data incoming from 4GL.
*
* @return The response data from the customer-specific application, which is sent back to
* the converted 4GL code.
*/
public static character remoteCall(character content)
{
if (content == null)
{
content = new character();
}
UnformattedPayload payload = new UnformattedPayload();
payload.payload = content.toStringMessage();
UnformattedPayload response = LogicalTerminal.remoteCall(payload);
return new character(response.payload);
}
/**
* This is a special API which is used to delegate a call from converted 4GL code
* (via _P2J_REMOTE_CALL_ functions) to the remote side. Usually, this is used when P2J client
* runs in embedded mode, i.e. the P2J screens are embedded in another customer-specific
* application. The call will be remoted into the customer-specific application.
* <p>
* This API is expected to block waiting for a response.
*
* @param content
* The request data incoming from 4GL.
*
* @return The response data from the customer-specific application, which is sent back to
* the converted 4GL code.
*/
public static character remoteCall(String content)
{
return remoteCall(new character(content));
}
/**
* This API is used when P2J client runs in embedded mode (i.e. the P2J screens are embedded
* into another customer-specific application). It allows the customer-specific to execute
* converted 4GL code. Only external programs, procedures and functions can be invoked. The
* result of this invocation (and any additional OUTPUT parameters, errors, etc) will be
* returned back to the customer-specific application.
*
* @param payload
* The details about the request.
*
* @return The response for this request.
*/
public static InvocationResponsePayload invoke(InvocationRequestPayload payload)
{
WorkArea wa = work.obtain();
handle h;
boolean persistent;
switch (payload.type)
{
case EXTERNAL_PROGRAM:
{
persistent = payload.persistent;
h = (persistent ? new handle() : null);
break;
}
default:
{
persistent = false;
long hid = payload.handle;
h = (hid == 0 ? null : handle.fromResourceId(hid));
if (h != null && h._isValid() && !wa.pm.isRemotePersistentProcedure(h.get()))
{
String msg = "Attempting to access a persistent procedure which was not obtained " +
"by the remote, customer-specific application!";
if (LOG.isLoggable(Level.SEVERE))
{
LOG.severe(msg);
}
throw new SilentUnwindException(new IllegalStateException(msg));
}
break;
}
}
character cname = new character(payload.name);
boolean function = payload.type == InvokeType.FUNCTION;
MapArgumentResolver argResolver = new MapArgumentResolver(payload.arguments);
BlockRunner block = new BlockRunner((BlockRunner br) ->
{
BaseDataType result = null;
work.obtain().remoteInvoke = true;
if (payload.type == InvokeType.EXTERNAL_PROGRAM)
{
invokeExternalProcedure(cname, persistent, false, false, h, false, null, null, argResolver);
result = wa.returnValue;
if (persistent && h != null && h._isValid())
{
wa.pm.registerRemotePersistentProcedure(h.get());
}
}
else
{
result = invokeImpl(wa.inHandleResolvers, h, cname, function,
false, false, false, false, null, null, argResolver);
if (payload.type == InvokeType.PROCEDURE)
{
// if it is a procedure, get the result from RETURN-VALUE
result = wa.returnValue;
}
}
return result;
},
() ->
{
// if the API was resolved and validated, the flag will be reset just before the entry
// is executed (so any downstream calls are not seen as "remote"); if there are any
// errors, and the entry could not be executed, the flag needs to be reset.
// so, we ensure the flag is reset here, always.
wa.remoteInvoke = false;
}, payload.noError);
block.execute();
InvocationResponsePayload response = new InvocationResponsePayload();
// the handle is sent always (so that the customer app knows in which handle the program
// was ran... so it can delete it (maybe)
response.handle = (h != null && h._isValid() ? h.getResourceId() : 0);
response.returnValue = (block.result == null ? null : block.result.toStringMessage());
response.error = block.error;
if (block.error)
{
response.errorNumber = block.errorNumber;
response.errorMessage = block.errorMessage;
}
response.stop = block.stop;
response.quit = block.quit;
response.outputArguments = argResolver.getOutputArguments();
return response;
}
/**
* Check if last attempted invoke failed because of invalid arguments.
*
* @return The state of the {@link WorkArea#invalidArgs} flag.
*/
public static boolean hadInvalidArguments()
{
return work.obtain().invalidArgs;
}
/**
* Resolve the specified legacy method.
*
* @param isPoly
* Flag indicating if the call is for a non-dynamic POLY call.
* @param type
* The legacy class where to look.
* @param name
* The legacy class name.
* @param method
* The legacy method name.
* @param isStatic
* Flag indicating if we are looking for a static method.
* @param modes
* The argument's modes. May be <code>null</code>.
* @param origArgs
* The arguments.
*
* @return The resolved internal entry.
*/
public static InternalEntry resolveLegacyEntry(boolean isPoly,
Class<?> type,
String name,
String method,
boolean isStatic,
String modes,
Object... origArgs)
{
return resolveLegacyEntry(work.obtain(), isPoly, true, isStatic, type, name, method, modes, origArgs);
}
/**
* The implementation of all function and procedure calls. This disambiguates and implements
* the following calls:
* <ul>
* <li>if <code>h</code> is not null, then this is a RUN ... IN handle call.</li>
* <li>if the <code>exports</code> is not null, then this is a RUN ... ON SERVER call, so
* only external programs will be searched. Note that this parameter will be not-null
* only when this call is executed on the remote, appserver, side.</li>
* <li>else, this is a RUN statement call with no special clauses.</li>
* </ul>
*
* @param h
* The handle to which the procedure belongs. If <code>null</code>, defaults to
* THIS-PROCEDURE.
* @param name
* The legacy 4GL name for the function or procedure.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The arguments.
*
* @return The return value from the block (or its nested blocks) which is the <code>unknown
* value</code> by default, on error or for procedures.
*/
public static BaseDataType invoke(handle h,
character name,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
String modes,
Object... args)
{
// h will never be a proxy procedure handle at the time of this call. if it ends up a
// remote procedure handle here, we have a problem in the runtime implementation
if (ProcedureManager.isProxy(h))
{
throw new IllegalArgumentException("The supplied procedure handle is a proxy handle!");
}
WorkArea wa = work.obtain();
List<Resolver> resolvers = null;
if (h != null)
{
// The target of a "RUN proc" stmt or DYNAMIC-FUNCTION call ends on
// first find in:
// - THIS-PROCEDURE
// - THIS-PROCEDURE:super-procedures
// - SESSION-super-procedures
resolvers = wa.inHandleResolvers;
}
else
{
// The target of a "RUN proc" stmt or DYNAMIC-FUNCTION call ends on
// first find in:
// - THIS-PROCEDURE
// - THIS-PROCEDURE:super-procedures
// - SESSION-super-procedures
// - if an external program with the given name is found (for the
// "RUN proc" statement without "IN handle" clause)
// - search distinguishes between procedures and functions with the
// same name. On first name match, will attempt to invoke it.
// - is not necessarily for the target procedure to have its header
// defined in the current procedure. Applies only to "RUN proc"
// statement.
// - "RUN proc0." and "proc0()" will end up calling different code
// (one is a super-proc, the other is a super-function).
// The body for these two may be in different super-procedures.
resolvers = wa.resolvers;
}
return invokeImpl(resolvers,
h,
name,
function,
dynamicFunction,
superCall,
transactionDistinct,
false,
null,
modes,
new ArrayArgumentResolver(args));
}
/**
* Resolve the external program with the specified name.
*
* @param name
* The legacy 4GL name for the function or procedure.
* @param exports
* The list of allowed external procedures to be invoked with a RUN ... ON SERVER
* statement.
*
* @return The instantiated (not yet initialized) {@link ExternalProgramWrapper}.
*/
public static ExternalProgramWrapper resolveExternalProcedure(String name, character exports)
{
WorkArea wa = work.obtain();
InternalEntryCaller caller = wa.externalResolver
.resolve(null, name, false, false, exports, true);
ExternalProgramWrapper extProg = null;
if (caller != null)
{
extProg = new ExternalProgramWrapper(caller.getCallerInstance());
wa.pm.addProcedure(new handle(extProg), name, true);
}
return extProg;
}
/**
* Initialize the specified external procedure. This is done by calling the associated
* <code>execute</code> method.
*
* @param extProg
* The associated external program.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The arguments.
*/
public static void initializeExternalProcedure(ExternalProgramWrapper extProg,
boolean transactionDistinct,
String modes,
Object... args)
{
WorkArea wa = work.obtain();
InternalEntryCaller caller = new ExternalProcedureCaller(wa, extProg.get());
// resolve the name early, as at the time the invokeError is called the proc handle is gone
String relName = wa.pm.getRelativeName(extProg.referent);
Exception deferred = null;
handle hProc = new handle(extProg);
try
{
if (!validArguments(wa, relName, caller, false, modes, args))
{
return;
}
boolean error = false;
try
{
Object referent = extProg.get();
Runnable push = () ->
{
wa.pm.pushCalleeInfo(referent.getClass(),
false, referent, referent,
ProcedureManager.EXTERNAL_PROGRAM, relName,
false, true);
};
caller.setPushWorker(push);
caller.invoke(modes, args);
}
catch (Exception e)
{
// any exceptions here cause the persistent procedure to no longer be available
error = true;
throw e;
}
finally
{
if (error || wa.em.isPending())
{
ProcedureManager.removePersistentProcedure(hProc);
}
}
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
throw ce;
}
// handle reflection exceptions
catch (NoSuchMethodException nsm)
{
invokeFailure(caller.getMethodName());
return;
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
// all other cases
catch (Exception exp)
{
deferred = exp;
}
if (deferred != null)
{
invokeError(caller, relName, deferred);
}
}
/**
* Execute the specified destructor in the given class.
*
* @param referent
* The legacy object.
* @param dtor
* The destructor.
*/
static void executeDestructor(_BaseObject_ referent, InternalEntry dtor)
{
WorkArea wa = work.obtain();
String relName = wa.pm.getRelativeName(referent);
Object saved = wa.tm.getNowIterating();
wa.tm.setNowIterating(null);
handle phandle = new handle(new ExternalProgramWrapper(referent));
InternalEntryCaller dtorCaller = new InternalEntryCaller(wa, phandle, dtor.jname, dtor.pname);
Runnable push = () ->
{
Class<?> def = dtor.getMethod().getDeclaringClass();
wa.pm.pushCalleeInfo(def, false, referent, referent, dtor.pname, relName, false, false);
};
dtorCaller.setPushWorker(push);
dtorCaller.ie = dtor;
if (!validArguments(wa, relName, dtorCaller, false, null))
{
return;
}
try
{
dtorCaller.invoke(null);
}
catch (Throwable t)
{
// ignore errors
// TODO: only from implicit delete - on explicit delete these need to be thrown back to
// the caller.
}
finally
{
wa.tm.setNowIterating(saved);
}
}
/**
* Given a legacy class, initialize it by executing its static constructor.
*
* @param cls
* The legacy class.
* @param name
* The legacy class name.
*
* @return <code>true</code> if the initialization completed or if there is no static
* constructor.
*/
static boolean initializeLegacyClass(Class<? extends _BaseObject_> cls, Object referent, String name)
{
WorkArea wa = work.obtain();
// if the class was loaded, then register it with the SourceNameMapper
SourceNameMapper.registerLegacyClass(cls);
handle phandle = new handle(new ExternalProgramWrapper(referent));
wa.pm.addProcedure(phandle, name, true, false, false, true);
InternalEntry iector = SourceNameMapper.getStaticConstructor(cls);
// resolve the name early, as at the time the invokeError is called the proc handle is gone
String relName = wa.pm.getRelativeName(referent);
Exception deferred = null;
try
{
BufferManager bm = BufferManager.get();
// call a ContextLocal.get() for any defined static member in this class, regardless of
// access mode; this is required so that the resources are created BEFORE the c'tor is
// invoked, so they get registered with the surrogate external program
Method mget = ContextLocal.class.getMethod("get");
String pkgroot = SourceNameMapper.getPackageRoot();
for (Field f : cls.getDeclaredFields())
{
if (!Modifier.isStatic(f.getModifiers()))
{
continue;
}
Class<?> ftype = f.getType();
if (ftype.isPrimitive() || ftype == InvokeConfig.class)
{
continue;
}
if (ftype.isArray() && ftype.getComponentType().isPrimitive())
{
continue;
}
String fname = f.getName();
if (fname.equals(TranslationManager.TR_MANAGER_FIELD_NAME) ||
fname.equals(TranslationManager.REFERENT_ID_FIELD_NAME))
{
continue;
}
if (!ContextLocal.class.isAssignableFrom(ftype))
{
String msg = "Field " + f.toString() + " in class " +
cls.toString() + " is static, but not ContextLocal!";
String fpkg = ftype.getPackage().getName();
if (!fpkg.startsWith("com.goldencode.p2j.util.logging") &&
(fpkg.startsWith("com.goldencode") || fpkg.startsWith(pkgroot)))
{
throw new IllegalStateException(msg);
}
else if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, msg);
}
continue;
}
f.setAccessible(true);
Object obj = mget.invoke(f.get(null));
if (obj instanceof BufferReference)
{
bm.registerPendingBufferClass(((BufferReference) obj).buffer(), cls);
}
}
boolean error = false;
try
{
Object[] args = null;
Method mtor = null;
try
{
mtor = iector == null ? null : cls.getMethod(iector.jname);
}
catch (Exception e)
{
// ignore
}
if (mtor == null)
{
// emulate an empty 'execute' method
mtor = ObjectOps.class.getDeclaredMethod("defaultStaticConstructor", Class.class);
mtor.setAccessible(true);
args = new Object[] { cls };
}
InternalEntryCaller ctorCaller = new ExternalProcedureCaller(wa, referent);
ctorCaller.mthd = mtor;
Class<?> def = mtor.getDeclaringClass();
Runnable push = () ->
{
wa.pm.pushCalleeInfo(def, false, referent, referent,
ProcedureManager.EXTERNAL_PROGRAM, relName,
false, true);
};
ctorCaller.setPushWorker(push);
ctorCaller.invoke(null, args);
}
catch (Exception e)
{
// any exceptions here cause the persistent procedure to no longer be available
error = true;
// no reason to re-throw, the method will return false in the finally block
}
finally
{
if (error || wa.em.isPending())
{
return false;
}
}
return true;
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
throw ce;
}
// handle reflection exceptions
catch (NoSuchMethodException nsm)
{
invokeFailure(name);
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
// all other cases
catch (Exception exp)
{
deferred = exp;
}
if (deferred != null)
{
invokeError(null, relName, deferred);
}
return true;
}
/**
* Given a legacy class instance, execute the initialization methods. These are comprised from
* the synthetic execute methods (for all super-classes), and the synthetic constructor method
* for this class.
*
* @param referent
* The object instance.
* @param name
* The instantiating legacy class name.
* @param executes
* The list of 'execute' initialization methods.
* @param modes
* The parameter modes for the constructor method.
* @param bypassConstructor
* Avoid executing the constructor method during initialization if <code>true</code>.
* @param args
* The arguments for the constructor method.
*
* @return <code>true</code> if the initialization completed.
*/
static boolean initializeLegacyObject(Object referent,
String name,
Method[] executes,
String modes,
boolean bypassConstructor,
Object... args)
{
WorkArea wa = work.obtain();
handle phandle = new handle(new ExternalProgramWrapper(referent));
wa.pm.addProcedure(phandle, name, true);
BufferManager.get().resolvePendingBufferClasses(referent);
InternalEntry iEntry = null;
if (!bypassConstructor)
{
List<InternalEntry> ctors = SourceNameMapper.getConstructors((Class) referent.getClass());
iEntry = resolveLegacyEntry(wa, false, true, false, referent.getClass(), name, null, modes, ctors, args);
if (iEntry == null)
{
// not found
return false;
}
}
// resolve the name early, as at the time the invokeError is called the proc handle is gone
String relName = wa.pm.getRelativeName(referent);
Exception deferred = null;
try
{
boolean error = false;
try
{
// the pending scopeables must be registered for each 'execute' or constructor call, as these
// belong to the instance, not the method.
Scopeable[] scopeables = wa.tm.getPendingScopeables();
try
{
Class<?> def = referent.getClass();
Body body = executes == null ? null : () ->
{
if (executes != null)
{
for (int i = 0; i < executes.length; i++)
{
try
{
executes[i].invoke(referent);
}
catch (ReflectiveOperationException e)
{
LOG.log(Level.SEVERE, "Error initializing legacy object of type " + relName, e);
}
}
}
};
BlockManager.externalProcedure(def, referent, TransactionType.NONE, new Block(body));
}
finally
{
wa.tm.setPendingScopeables(scopeables);
}
if (!bypassConstructor)
{
InternalEntry ie = iEntry;
InternalEntryCaller ctorCaller = new InternalEntryCaller(wa, phandle, ie.jname, ie.pname);
ctorCaller.mthd = ie.getMethod();
ctorCaller.ie = ie;
prepareArguments(ie, args);
if (!validArguments(wa, name, ctorCaller, false, modes, args))
{
return false;
}
// use the method resolved after arg validation.
ctorCaller.mthd = ie.getMethod();
Runnable push = () ->
{
Class<?> def = ie.getMethod().getDeclaringClass();
wa.pm.pushCalleeInfo(def, false, referent, referent, ie.pname, relName, false, false);
};
ctorCaller.setPushWorker(push);
ctorCaller.invoke(null, args);
}
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
error = true;
throw ce;
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
finally
{
wa.tm.deregisterOutputParameterAssigner();
if (!error && wa.em.isPending())
{
return false;
}
}
return true;
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
throw ce;
}
// handle reflection exceptions
catch (NoSuchMethodException nsm)
{
invokeFailure(name);
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
// all other cases
catch (Exception exp)
{
deferred = exp;
}
if (deferred != null)
{
invokeError(null, relName, deferred);
}
return true;
}
/**
* Invoke the specified legacy method in a legacy OO class.
*
* @param voidReturn
* Flag indicating that the method can return void.
* @param referent
* The referent where the method belongs. This can be a {@link Class} instance in
* case of static methods or a {@link _BaseObject_} instance in case of instance
* methods.
* @param name
* The legacy class name.
* @param method
* The legacy method name.
* @param modes
* The argument's modes. May be <code>null</code>.
* @param args
* The arguments.
*
* @return The returned value or <code>null</code> if this is a void method.
*/
static Object invokeLegacyMethod(boolean voidReturn,
Object referent,
String name,
String method,
String modes,
Object[] args)
{
boolean isStatic = referent instanceof Class;
Class<? extends _BaseObject_> type;
if (isStatic)
{
type = (Class<? extends _BaseObject_>) referent;
referent = ObjectOps.getStaticInstance(type);
}
else
{
type = (Class<? extends _BaseObject_>) referent.getClass();
}
InternalEntry ie = resolveLegacyEntry(work.obtain(), false, true, isStatic, type, name, method, modes, args);
if (ie == null && !isStatic)
{
isStatic = true;
type = (Class<? extends _BaseObject_>) referent.getClass();
referent = ObjectOps.getStaticInstance(type);
// allow a static method to be invoked from an instance context
ie = resolveLegacyEntry(work.obtain(), false, true, isStatic, type, name, method, modes, args);
}
if (ie == null)
{
// not found
ErrorManager.recordOrThrowError(14457,
"Could not locate method '" + method + "' with " +
"matching signature in class '" + name + "'",
false);
return unknown.UNKNOWN;
}
return invokeLegacyMethod(ie, voidReturn, referent, name, method, modes, args);
}
/**
* Invoke the specified legacy method in a legacy OO class.
*
* @param ie
* The target entry associated with this method.
* @param voidReturn
* Flag indicating that the method can return void.
* @param referent
* The referent where the method belongs. This can be a {@link Class} instance in
* case of static methods or a {@link _BaseObject_} instance in case of instance
* methods.
* @param name
* The legacy class name.
* @param method
* The legacy method name.
* @param modes
* The argument's modes. May be <code>null</code>.
* @param args
* The arguments.
*
* @return The returned value or <code>null</code> if this is a void method.
*/
public static Object invokeLegacyMethod(InternalEntry ie,
boolean voidReturn,
Object referent,
String name,
String method,
String modes,
Object[] args)
{
Method mthd = ie.getMethod();
boolean voidMethod = mthd.getReturnType() == void.class ||
mthd.getReturnType() == Void.class;
// if void return is not allowed, and the method is void, then fail
if (!voidReturn && voidMethod)
{
String msg = String.format("Invalid use of dynamic invoke of VOID method '%s' in an " +
"expression", method);
ErrorManager.recordOrThrowError(15304, msg, false, false);
return unknown.UNKNOWN;
}
WorkArea wa = work.obtain();
// the method was found
handle phandle = new handle(new ExternalProgramWrapper(referent));
InternalEntryCaller caller = new InternalEntryCaller(wa, phandle, mthd.getName(), method);
caller.mthd = ie.getMethod();
caller.ie = ie;
prepareArguments(ie, args);
if (!validArguments(wa, method, caller, false, modes, args))
{
return unknown.UNKNOWN;
}
// resolve the name early, as at the time the invokeError is called the proc handle is gone
String relName = wa.pm.getRelativeName(referent);
Exception deferred = null;
try
{
try
{
Object ref = referent;
Runnable push = () ->
{
Class<?> def = ie.getMethod().getDeclaringClass();
wa.pm.pushCalleeInfo(def, false, ref, ref, method, relName, true, false);
wa.pm.peekCalleeInfo().setLegacyName(ie.pname);
};
caller.setPushWorker(push);
caller.mthd = mthd;
mthd.setAccessible(true);
return caller.invoke(ie.getParameterModes(), args);
}
finally
{
wa.tm.deregisterOutputParameterAssigner();
}
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
throw ce;
}
// handle reflection exceptions
catch (NoSuchMethodException nsm)
{
invokeFailure(name);
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
// all other cases
catch (Exception exp)
{
deferred = exp;
}
if (deferred != null)
{
invokeError(null, relName, deferred);
}
return null;
}
/**
* Prepare the arguments to be passed to the {@link ControlFlowOps} APIs. This requires
* wrapping any output parameters and other extent, OUTPUT or INPUT-OUTPUT related work.
*
* @param ie
* The internal entry to be executed.
* @param args
* The arguments.
*/
private static void prepareArguments(InternalEntry ie, Object...args)
{
LegacySignature lsig = ie.getMethod() == null
? null
: ie.getMethod().getAnnotation(LegacySignature.class);
String modes = ie.getParameterModes();
for (int i = 0; i < args.length; i++)
{
char mode = modes != null && i < modes.length() ? modes.charAt(i) : InternalEntry.INPUT_MODE;
Object arg = args[i];
if (arg instanceof BaseDataTypeVariable)
{
arg = ((BaseDataTypeVariable) arg).get();
args[i] = arg;
}
else if (arg instanceof Resolvable)
{
Resolvable r = (Resolvable) arg;
arg = r.resolve();
args[i] = arg;
if (mode == InternalEntry.INPUT_MODE && r.getType() == integer.class && arg instanceof int64)
{
arg = InputParameter.cast(integer.class, (int64) arg);
args[i] = arg;
}
}
if (mode == InternalEntry.OUTPUT_MODE || mode == InternalEntry.INPUT_OUTPUT_MODE)
{
boolean io = mode == InternalEntry.INPUT_OUTPUT_MODE;
if (arg instanceof FieldReference)
{
FieldReference fr = (FieldReference) arg;
if (fr.getExtent() != null)
{
args[i] = OutputParameter.wrap(fr.getParentBuffer().getDMOProxy(),
fr.getProperty(),
(Class) fr.getType(),
fr.getExtent().intValue(),
io);
}
else
{
args[i] = OutputParameter.wrap(fr.getParentBuffer().getDMOProxy(),
fr.getProperty(),
(Class) fr.getType(),
io);
}
}
else if (arg instanceof PropertyReference)
{
PropertyReference fr = (PropertyReference) arg;
args[i] = fr.wrap(io);
}
else if (arg instanceof BaseDataType)
{
if (lsig == null)
{
args[i] = OutputParameter.wrap((BaseDataType) arg, io);
}
else
{
LegacyParameter lpar = i < lsig.parameters().length ? lsig.parameters()[i] : null;
if (lpar != null && lpar.type().equals("JOBJECT") &&
lpar.qualified() != null &&
!lpar.qualified().isEmpty())
{
if (arg instanceof jobject)
{
// nothing to do, just wrap
args[i] = OutputParameter.wrap((BaseDataType) arg, io);
}
else
{
// wrap to Java
try
{
args[i] = OutputParameter.wrapToJava(Class.forName(lpar.qualified()),
(BaseDataType) arg,
io);
}
catch (ClassNotFoundException e)
{
throw new RuntimeException("Unexpected qualified type " +
lpar.qualified(), e);
}
}
}
else if (arg instanceof jobject)
{
// wrap from Java
args[i] = OutputParameter.wrapFromJava(BaseDataType.fromTypeName(lpar.type()),
(jobject<?>) arg,
io);
}
}
}
else if (!(arg instanceof AbstractExtentParameter ||
arg instanceof TableParameter ||
arg instanceof DataSetParameter))
{
throw new RuntimeException("Unexpected OUTPUT arg " + arg);
}
}
else if (arg instanceof PropertyReference)
{
PropertyReference fr = (PropertyReference) arg;
args[i] = fr.get();
}
}
}
/**
* Normalize the given argument to be compatible to a BDT type.
*
* @param arg
* The argument.
* @param sigType
* The argument's original type.
* @param candidateType
* The method definition parameter type.
*/
private static Object normalizeType(Object arg, Class<?> sigType, Class<?> candidateType)
{
// convert non-BDT argument to BDT
if (arg instanceof BaseDataTypeVariable)
{
arg = ((BaseDataTypeVariable) arg).get();
}
else if (!BaseDataType.class.isAssignableFrom(sigType) &&
!(sigType.isArray() ||
arg instanceof AbstractParameter ||
arg instanceof FieldReference ||
arg instanceof PropertyReference))
{
Object aval = null;
if (sigType == Integer.class)
{
if (candidateType == integer.class)
{
aval = new integer((Integer) arg);
}
else if (candidateType == int64.class)
{
aval = new int64((Integer) arg);
}
else if (candidateType == decimal.class)
{
aval = new decimal((Integer) arg);
}
else if (candidateType == recid.class)
{
aval = new recid((Integer) arg);
}
}
else if (sigType == Long.class)
{
if (candidateType == integer.class)
{
aval = new integer((Long) arg);
}
else if (candidateType == int64.class)
{
aval = new int64((Long) arg);
}
else if (candidateType == decimal.class)
{
aval = new decimal((Long) arg);
}
else if (candidateType == recid.class)
{
aval = new recid((Long) arg);
}
}
else if (sigType == Double.class)
{
if (candidateType == integer.class)
{
aval = new integer((Double) arg);
}
else if (candidateType == int64.class)
{
aval = new int64((Double) arg);
}
else if (candidateType == decimal.class)
{
aval = new decimal((Double) arg);
}
}
else if (sigType == Float.class)
{
if (candidateType == integer.class)
{
aval = new integer((Float) arg);
}
else if (candidateType == int64.class)
{
aval = new int64((Float) arg);
}
else if (candidateType == decimal.class)
{
aval = new decimal((Float) arg);
}
}
else if (sigType == Boolean.class)
{
aval = new logical((Boolean) arg);
}
else if (sigType == String.class)
{
aval = new character((String) arg);
}
if (aval != null)
{
arg = aval;
}
else
{
// TODO: can't convert, what next?
}
}
return arg;
}
/**
* Resolve the specified legacy method.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param isPoly
* Flag indicating if the call is for a non-dynamic POLY call.
* @param firstType
* Flag indicating if we are looking in the first type (this is not for a super-type).
* @param isStatic
* Flag indicating if we are looking for a static method.
* @param type
* The legacy class where to look.
* @param name
* The legacy class name.
* @param method
* The legacy method name.
* @param modes
* The argument's modes. May be <code>null</code>.
* @param origArgs
* The arguments.
*
* @return The resolved internal entry.
*/
private static InternalEntry resolveLegacyEntry(WorkArea wa,
boolean isPoly,
boolean firstType,
boolean isStatic,
Class<?> type,
String name,
String method,
String modes,
Object... origArgs)
{
if (type == null || !_BaseObject_.class.isAssignableFrom(type))
{
// reached beyond last 'legacy superclass', nowhere else to search
return null;
}
// get all overrides for this legacy method name
List<InternalEntry> overrides = SourceNameMapper.getOverrides(type, method);
return resolveLegacyEntry(wa, isPoly, firstType, isStatic, type, name, method, modes, overrides, origArgs);
}
/**
* Given a list of overloaded methods, resolve the target matching the passed arguments.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param isPoly
* Flag indicating if the call is for a non-dynamic POLY call.
* @param firstType
* Flag indicating if we are looking in the first type (this is not for a super-type).
* @param isStatic
* Flag indicating if we are looking for a static method.
* @param type
* The legacy class where to look.
* @param name
* The legacy class name.
* @param method
* The legacy method name.
* @param modes
* The argument's modes. May be <code>null</code>.
* @param overloads
* The list of method overloads.
* @param origArgs
* The arguments.
*
* @return The resolved internal entry.
*/
private static InternalEntry resolveLegacyEntry(WorkArea wa,
boolean isPoly,
boolean firstType,
boolean isStatic,
Class<?> type,
String name,
String method,
String modes,
List<InternalEntry> overloads,
Object... origArgs)
{
prepareArgs(modes, origArgs);
// (argument, method)
final Class<?>[][] validDatasetPairs =
{
{ InputDataSetParameter.class, InputDataSetHandle.class },
{ InputDataSetHandle.class, InputDataSetParameter.class },
{ OutputDataSetParameter.class, OutputDataSetHandle.class },
{ OutputDataSetHandle.class, OutputDataSetParameter.class },
{ InputOutputDataSetParameter.class, InputOutputDataSetHandle.class },
{ InputOutputDataSetHandle.class, InputOutputDataSetParameter.class }
};
// (argument, method)
final Class<?>[][] validTablePairs =
{
{ InputTableParameter.class, InputTableHandle.class },
{ InputTableHandle.class, InputTableParameter.class },
{ OutputTableParameter.class, OutputTableHandle.class },
{ OutputTableHandle.class, OutputTableParameter.class },
{ InputOutputTableParameter.class, InputOutputTableHandle.class },
{ InputOutputTableHandle.class, InputOutputTableParameter.class }
};
// refine the methods based on the signature
int argSize = origArgs.length;
Iterator<InternalEntry> iter = overloads.iterator();
while (iter.hasNext())
{
InternalEntry ie = iter.next();
String pmodes = ie.getParameterModes();
int psize = ie.getParameterListSize();
Method m = ie.getMethod();
Class<?>[] mtypes = m == null ? null : m.getParameterTypes();
// the signature match is different from validating a procedure/function signature: there
// is no automatic conversion from one type to another. only widening is possible.
if (argSize != psize)
{
iter.remove();
continue;
}
Object[] args = new Object[origArgs.length];
System.arraycopy(origArgs, 0, args, 0, argSize);
for (int i = 0; i < argSize; i++)
{
Object arg = args[i];
Class<?> sigType = arg.getClass();
if (sigType.isAnonymousClass())
{
sigType = sigType.getSuperclass();
}
if (arg instanceof Resolvable)
{
Resolvable r = (Resolvable) arg;
sigType = r.getType();
arg = r.resolve();
}
if (mtypes != null && mtypes[i] == sigType && object.class != sigType)
{
args[i] = arg;
continue;
}
Parameter param = ie.getParameter(i);
String ptype = param.getType();
Class<?> candidateType = BaseDataType.fromTypeName(ptype);
args[i] = normalizeType(arg, sigType, candidateType);
}
boolean ok = true;
for (int i = 0; i < psize; i++)
{
if (modes != null && pmodes.charAt(i) != modes.charAt(i))
{
// modes not matching
ok = false;
break;
}
Object arg = args[i];
if (arg.getClass() == unknown.class)
{
continue;
}
final Class<?> sigType = calculateType(arg);
if (mtypes != null && mtypes[i] == sigType && object.class != sigType)
{
continue;
}
// check dataset/dataset-handle and table/table-handl;e
Class<?>[][] dsTbPairs = null;
if (DataSetParameter.class.isAssignableFrom(sigType))
{
dsTbPairs = validDatasetPairs;
}
else if (TableParameter.class.isAssignableFrom(sigType))
{
dsTbPairs = validTablePairs;
}
if (dsTbPairs != null)
{
boolean found = false;
for (Class<?>[] pair : dsTbPairs)
{
if (pair[0] == sigType && pair[1] == mtypes[i])
{
found = true;
break;
}
}
if (found)
{
continue;
}
}
if (arg instanceof BaseDataTypeVariable)
{
arg = ((BaseDataTypeVariable) arg).get();
}
Parameter param = ie.getParameter(i);
String ptype = param.getType();
Class<?> candidateType = BaseDataType.fromTypeName(ptype);
char candidateMode = pmodes.charAt(i);
if (unknown.class == sigType)
{
// TODO: this can be ambiguous...
arg = BaseDataType.generateUnknown(candidateType);
args[i] = arg;
}
if (param.getExtent() != SourceNameMapper.NO_EXTENT)
{
// TODO: match via AbstractParameter and AbstractExtentParameter
int candidateExtent = param.getExtent();
int sigExtent = arg.getClass().isArray()
? Array.getLength(arg)
: SourceNameMapper.NO_EXTENT;
if (sigExtent == SourceNameMapper.NO_EXTENT)
{
if ((param.getMode().equals("OUTPUT") && arg instanceof OutputExtentParameter) ||
(param.getMode().equals("INPUT-OUTPUT") && arg instanceof InputOutputExtentParameter))
{
// TODO: array lengths ?? dynamic vs extent
continue;
}
//TODO: Trying to match methods with the wrong extent?
// Trying to match methods with given parameters represending the unknown value?
ok = false;
break;
}
boolean okExtent = true;
// depending on modes
if (sigExtent != candidateExtent)
{
if (candidateMode == InternalEntry.OUTPUT_MODE)
{
// only dynamic to non-dynamic
if (sigExtent != SourceNameMapper.DYNAMIC_EXTENT)
{
okExtent = false;
}
}
else if (candidateMode == InternalEntry.INPUT_MODE)
{
// only non-dynamic to dynamic
if (sigExtent == SourceNameMapper.DYNAMIC_EXTENT)
{
okExtent = false;
}
}
else if (candidateMode == InternalEntry.INPUT_OUTPUT_MODE)
{
// only the same
okExtent = false;
}
}
if (!okExtent)
{
// TODO: 4GL generates error... can't do this easily for dynamic mode.
ok = false;
break;
}
}
Class<?> clegacy = null;
Class<?> slegacy = null;
boolean untypedSigRef = false;
if (candidateType == object.class && sigType == object.class)
{
object<?> objectArg = null;
if (arg.getClass().isArray())
{
Object[] array = (Object[]) arg;
for (int j = 0; j < array.length; j++)
{
if (array[j] != null)
{
objectArg = (object<?>) array[j];
if (!((object<?>) array[j]).isUnknown())
{
break;
}
}
}
}
else
{
objectArg = (object<?>) arg;
}
slegacy = objectArg != null ? objectArg.type() : null;
untypedSigRef = slegacy == null && (objectArg == null || objectArg.isUnknown());
if (objectArg != null && objectArg._isValid())
{
untypedSigRef = false;
// for POLY invocations, we care about the declared type, and not the actual type
if (!isPoly)
{
slegacy = objectArg.ref.getClass();
}
}
clegacy = param.getQualified() == null ? _BaseObject_.class
: ObjectOps.resolveClass(param.getQualified());
// qualified parameter name might be a super class/interface
if (clegacy == null && !untypedSigRef)
{
clegacy = ObjectOps.getAssignableClass(param.getQualified(),
(Class<? extends _BaseObject_>) slegacy);
}
// if not qualified defaults to P.L.O (interface)
if (clegacy == null || clegacy.equals(BaseObject.class))
{
clegacy = _BaseObject_.class;
}
}
else if (candidateType == jobject.class && sigType == jobject.class)
{
try
{
clegacy = Class.forName(param.getQualified());
object<?> objectArg = null;
if (arg.getClass().isArray())
{
Object[] array = (Object[]) arg;
for (int j = 0; j < array.length; j++)
{
if (array[j] != null)
{
objectArg = (object<?>) array[j];
}
}
}
else
{
objectArg = (object<?>) arg;
}
slegacy = objectArg != null ? objectArg.type() : null;
}
catch (ClassNotFoundException e)
{
throw new RuntimeException(e);
}
}
if (candidateType == jobject.class)
{
Class<?> atype = null;
try
{
atype = Class.forName(param.getQualified());
}
catch (ClassNotFoundException e)
{
throw new RuntimeException(e);
}
// if is one of the primitive types, then let it go
if (atype == java.lang.Integer.class ||
atype == java.lang.Short.class ||
atype == java.lang.Byte.class)
{
candidateType = integer.class;
}
else if (atype == java.lang.Double.class ||
atype == java.lang.Float.class ||
atype == java.math.BigDecimal.class)
{
candidateType = decimal.class;
}
else if (atype == java.lang.Boolean.class)
{
candidateType = logical.class;
}
else if (atype == java.lang.String.class)
{
candidateType = character.class;
}
else if (atype == java.util.Date.class)
{
candidateType = date.class;
}
else if (atype == java.sql.Timestamp.class)
{
candidateType = datetime.class;
}
}
// sigType/slegacy are the argument's type
// candidateType/clegacy are the method parameter's type.
if (candidateType != sigType)
{
// check the parameter mode at the definition
if (candidateMode == InternalEntry.INPUT_MODE)
{
// widening matches are OK, narrowing are NOT OK
if ((int64.class == sigType && decimal.class == candidateType) ||
(integer.class == sigType && (decimal.class == candidateType ||
int64.class == candidateType)) ||
(character.class == sigType && longchar.class == candidateType) ||
(datetime.class == sigType && datetimetz.class == candidateType) ||
(date.class == sigType && (datetime.class == candidateType ||
datetimetz.class == candidateType)))
{
// ok
}
else if (candidateType == object.class && sigType == object.class)
{
// for INPUT, 4GL allows only the same type or a sub-class
// of the parameter's type
if (!untypedSigRef && !clegacy.isAssignableFrom(slegacy))
{
ok = false;
break;
}
// ok
}
else if (candidateType == jobject.class && sigType == jobject.class)
{
// for INPUT, 4GL allows only the same type or a sub-class
// of the parameter's type
if (!untypedSigRef && !clegacy.isAssignableFrom(slegacy))
{
ok = false;
break;
}
// ok
}
else
{
// not a match
ok = false;
break;
}
}
else if (candidateMode == InternalEntry.OUTPUT_MODE)
{
// narrowing matches are OK, widening are NOT OK
if ((int64.class == sigType && integer.class == candidateType) ||
(decimal.class == sigType && (integer.class == candidateType ||
int64.class == candidateType)) ||
(longchar.class == sigType && character.class == candidateType) ||
(datetime.class == sigType && date.class == candidateType) ||
(datetimetz.class == sigType && (date.class == candidateType ||
datetime.class == candidateType)))
{
// ok
}
else if (candidateType == object.class && sigType == object.class)
{
// for OUTPUT, 4GL allows only the same type or a super-class
// of the parameter's type
if (!untypedSigRef && !slegacy.isAssignableFrom(clegacy))
{
ok = false;
break;
}
// ok
}
else if (candidateType == jobject.class && sigType == jobject.class)
{
// for OUTPUT, 4GL allows only the same type or a super-class
// of the parameter's type
if (!untypedSigRef && !slegacy.isAssignableFrom(clegacy))
{
ok = false;
break;
}
// ok
}
else
{
ok = false;
break;
}
}
else if (candidateMode == InternalEntry.INPUT_OUTPUT_MODE)
{
// INPUT-OUTPUT requires exact match
if (candidateType == object.class && sigType == object.class)
{
if (!untypedSigRef && clegacy != slegacy)
{
ok = false;
break;
}
}
else if (candidateType == jobject.class && sigType == jobject.class)
{
if (!untypedSigRef && clegacy != slegacy)
{
ok = false;
break;
}
}
// ok
}
}
else if (candidateType == object.class && sigType == object.class)
{
// check the parameter mode at the definition
if (candidateMode == InternalEntry.INPUT_MODE)
{
// for INPUT, 4GL allows only the same type or a sub-class
// of the parameter's type
if (!untypedSigRef && !clegacy.isAssignableFrom(slegacy))
{
ok = false;
break;
}
// ok
}
else if (candidateMode == InternalEntry.OUTPUT_MODE)
{
// for OUTPUT, 4GL allows only the same type or a super-class
// of the parameter's type
if (!untypedSigRef && !slegacy.isAssignableFrom(clegacy))
{
ok = false;
break;
}
// ok
}
else if (candidateMode == InternalEntry.INPUT_OUTPUT_MODE)
{
// INPUT-OUTPUT requires exact match
if (!untypedSigRef && clegacy != slegacy)
{
ok = false;
break;
}
// ok
}
}
}
if (!ok)
{
// no match
iter.remove();
}
}
if (overloads.isEmpty())
{
return method == null
? null
: resolveLegacyEntry(wa, isPoly, false, isStatic, type.getSuperclass(), name, method, modes, origArgs);
}
// step 2: resolve defined methods based on the access mode
// a. if we are not in a legacy class, then only PUBLIC methods are allowed
// b. if we are in a legacy class and referent's class is not the same as or a super-class
// of THIS-OBJECT, then only PUBLIC methods are allowed
// c. if we are in a legacy class and referent's class is not the same as but is a
// super-class of THIS-OBJECT, then only PUBLIC and PROTECTED methods are allowed
// d. if we are in a legacy class and THIS-OBJECT is the same as referent's class, then
// any mode is allowed
boolean canPrivate = true;
boolean canProtected = true;
Object thisRef = wa.pm._thisProcedure();
Class<?> thisClass = thisRef == null ? null : thisRef.getClass();
if (thisClass == Object.class)
{
// convert it to the type where the static call is performed
thisClass = ObjectOps.getStaticClass(thisRef);
}
// an Object can be a 'root level block' in case of remote instantiation via an appserver
if (thisClass == null ||
!_BaseObject_.class.isAssignableFrom(thisClass) ||
!type.isAssignableFrom(thisClass))
{
// cases 2.a. and 2.b.
canPrivate = false;
canProtected = false;
}
else if (!firstType || type != wa.pm.getExecuting())
{
// case 2.c. - private methods can't be invoked if we are not in the same type as the executing type,
// or if we went in a super-class
canPrivate = false;
}
if (overloads.size() > 1)
{
// if we are not in the same class, remove the non-public ones
if (!(canPrivate || canProtected))
{
iter = overloads.iterator();
while (iter.hasNext())
{
InternalEntry ie = iter.next();
Method mthd = ie.getMethod();
int mods = mthd.getModifiers();
if ((!canPrivate && Modifier.isPrivate(mods)) ||
(!canProtected && Modifier.isProtected(mods)))
{
iter.remove();
}
}
}
if (overloads.isEmpty())
{
return resolveLegacyEntry(wa, isPoly, false, isStatic, type.getSuperclass(), name, method, modes, origArgs);
}
}
if (overloads.size() > 1)
{
// check if there is an exact match
for (InternalEntry ie : overloads)
{
Method m = ie.getMethod();
Class<?>[] mtypes = m == null ? null : m.getParameterTypes();
boolean ok = true;
for (int i = 0; i < argSize; i++)
{
Object arg = origArgs[i];
Class<?> sigType = calculateType(arg);
if (sigType.isAnonymousClass())
{
sigType = sigType.getSuperclass();
}
Class<?> candidateType = mtypes[i];
if (candidateType != sigType)
{
arg = normalizeType(arg, sigType, candidateType);
sigType = arg.getClass();
}
if (candidateType != sigType)
{
ok = false;
break;
}
int candidateExtent = ie.getParameter(i).getExtent();
int sigExtent = arg.getClass().isArray()
? Array.getLength(arg)
: SourceNameMapper.NO_EXTENT;
if (candidateExtent != sigExtent)
{
ok = false;
break;
}
}
if (ok)
{
return ie;
}
}
// TODO: before fuzzy searching, we need exact matches in super-classes; if none found, perform
// fuzzy match
FuzzyMethodRt fuzzy = new FuzzyMethodRt((Class<? extends _BaseObject_>) type, overloads);
InternalEntry ie = fuzzy.lookup(thisClass, name, isStatic, modes, origArgs);
if (ie != null)
{
// remove everything except what was found
overloads.removeIf((t) -> !t.getMethod().equals(ie.getMethod()));
}
else
{
// complete fail
return null;
}
}
InternalEntry ie = overloads.get(0);
Method mthd = ie.getMethod();
int mods = mthd.getModifiers();
// step 1: check static vs non-static methods (depending on the call type).
if (Modifier.isStatic(mods) != isStatic)
{
// an incorrect match was found, don't search any more
String msg = (isStatic
? "Non-static method '%s' of class '%s' may not be invoked as if it were static"
: "Static method '%s' may not be invoked via an instance of class '%s'");
int num = (isStatic ? 15320 : 15319);
msg = String.format(msg, method, name);
ErrorManager.recordOrThrowError(num, msg, false, false);
return null;
}
if ((!canPrivate && Modifier.isPrivate(mods)) ||
(!canProtected && Modifier.isProtected(mods)))
{
// an incorrect match was found, don't search any more
// TODO: error
return null;
}
Class<?>[] mtypes = ie.getMethod().getParameterTypes();
for (int i = 0; i < origArgs.length; i++)
{
if (mtypes[i].isAssignableFrom(origArgs[i].getClass()))
{
int expectedExtent = ie.getParameter(i).getExtent();
if (expectedExtent != SourceNameMapper.NO_EXTENT)
{
if (!origArgs[i].getClass().isArray())
{
// Match on wrong method
return null;
}
// If the coresponding parameter has dynamic extent type or
// the extents match then it's ok
if (!(expectedExtent == 0 || expectedExtent == Array.getLength(origArgs[i])))
{
// Match on wrong method
return null;
}
else
{
continue;
}
}
continue;
}
if (mtypes[i] == InputDataSetHandle.class)
{
origArgs[i] = new InputDataSetHandle((InputDataSetParameter) origArgs[i]);
}
else if (mtypes[i] == OutputDataSetHandle.class)
{
origArgs[i] = new OutputDataSetHandle((OutputDataSetParameter) origArgs[i]);
}
else if (mtypes[i] == InputOutputDataSetHandle.class)
{
origArgs[i] = new InputOutputDataSetHandle((InputOutputDataSetParameter) origArgs[i]);
}
else if (mtypes[i] == InputDataSetParameter.class)
{
origArgs[i] = new InputDataSetParameter((InputDataSetHandle) origArgs[i]);
}
else if (mtypes[i] == OutputDataSetParameter.class)
{
origArgs[i] = new OutputDataSetParameter((OutputDataSetHandle) origArgs[i]);
}
else if (mtypes[i] == InputOutputDataSetParameter.class)
{
origArgs[i] = new InputOutputDataSetParameter((InputOutputDataSetHandle) origArgs[i]);
}
// TABLE/TABLE-HANDLE
else if (mtypes[i] == InputTableHandle.class)
{
origArgs[i] = new InputTableHandle((InputTableParameter) origArgs[i]);
}
else if (mtypes[i] == OutputTableHandle.class)
{
origArgs[i] = new OutputTableHandle((OutputTableParameter) origArgs[i]);
}
else if (mtypes[i] == InputOutputTableHandle.class)
{
origArgs[i] = new InputOutputTableHandle((InputOutputTableParameter) origArgs[i]);
}
else if (mtypes[i] == InputTableParameter.class)
{
origArgs[i] = new InputTableParameter((InputTableHandle) origArgs[i]);
}
else if (mtypes[i] == OutputTableParameter.class)
{
origArgs[i] = new OutputTableParameter((OutputTableHandle) origArgs[i]);
}
else if (mtypes[i] == InputOutputTableParameter.class)
{
origArgs[i] = new InputOutputTableParameter((InputOutputTableHandle) origArgs[i]);
}
if (!mtypes[i].isAssignableFrom(origArgs[i].getClass()))
{
if (origArgs[i] instanceof BaseDataType && BaseDataType.class.isAssignableFrom(mtypes[i]))
{
// last resort - try to convert
try
{
Constructor<?> ctor = mtypes[i].getConstructor(BaseDataType.class);
origArgs[i] = ctor.newInstance(origArgs[i]);
}
catch (ReflectiveOperationException exc)
{
exc.printStackTrace();
// TODO: what ?
}
}
else
{
// TODO: what to do?
}
}
}
return ie;
}
/**
* Given an instance passed as an argument to a method call, calculate its actual type, for cases such when
* the value is wrapped in field or property reference, etc.
*
* @param arg
* The argument.
*
* @return The actual type.
*/
static Class<?> calculateType(final Object arg)
{
Class<?> sigType = arg.getClass();
if (sigType.isAnonymousClass())
{
sigType = sigType.getSuperclass();
}
if (arg instanceof BaseDataTypeVariable)
{
sigType = arg.getClass();
}
else if (arg instanceof FieldReference)
{
sigType = ((FieldReference) arg).getType();
}
else if (arg instanceof PropertyReference)
{
sigType = ((PropertyReference) arg).getType();
}
else if (arg instanceof ObjectVar)
{
sigType = object.class;
}
else if (arg instanceof BaseDataTypeConstant)
{
// resolve the super-class type for this constant.
sigType = sigType.getSuperclass();
}
if (unknown.class == sigType)
{
sigType = null;
}
if (sigType == null)
{
// TODO: BUFFER or TEMP-TABLE, how to match...
}
if (sigType.isArray())
{
sigType = sigType.getComponentType();
}
if (sigType == jobject.class)
{
Class<?> atype = ((jobject) arg).type();
// if is one of the primitive types, then let it go
if (atype == java.lang.Integer.class ||
atype == java.lang.Short.class ||
atype == java.lang.Byte.class)
{
sigType = integer.class;
}
else if (atype == java.lang.Double.class ||
atype == java.lang.Float.class ||
atype == java.math.BigDecimal.class)
{
sigType = decimal.class;
}
else if (atype == java.lang.Boolean.class)
{
sigType = logical.class;
}
else if (atype == java.lang.String.class)
{
sigType = character.class;
}
else if (atype == java.util.Date.class)
{
sigType = date.class;
}
else if (atype == java.sql.Timestamp.class)
{
sigType = datetime.class;
}
}
return sigType;
}
/**
* The implementation of all RUN ... PERSISTENT calls or appserver calls for an external
* procedure. If the <code>exports</code> parameter is not null, then the ON SERVER clause is
* in effect; this parameter can be not-null only on the remote, appserver, side.
*
* @param name
* The legacy 4GL name for the function or procedure.
* @param persistent
* Flag indicating that this call needs to be persistent.
* @param h
* The handle where to save the persistent procedure.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param exports
* The list of allowed external procedures to be invoked with a RUN ... ON SERVER
* statement.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The arguments.
*/
public static void invokeExternalProcedure(character name,
boolean persistent,
handle h,
boolean transactionDistinct,
character exports,
String modes,
Object... args)
{
invokeExternalProcedure(name,
persistent,
false,
false,
h,
transactionDistinct,
exports,
modes,
new ArrayArgumentResolver(args));
}
/**
* The implementation of all RUN ... PERSISTENT calls or appserver calls for an external
* procedure. If the <code>exports</code> parameter is not null, then the ON SERVER clause is
* in effect; this parameter can be not-null only on the remote, appserver, side.
*
* @param name
* The legacy 4GL name for the function or procedure.
* @param persistent
* Flag indicating that this call needs to be persistent.
* @param singleton
* Flag indicating that this call needs to be ran as singleton.
* @param singleRun
* Flag indicating that this call needs to be ran as single-run.
* @param h
* The handle where to save the persistent procedure.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param exports
* The list of allowed external procedures to be invoked with a RUN ... ON SERVER
* statement.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The arguments.
*/
static void invokeExternalProcedure(character name,
boolean persistent,
boolean singleton,
boolean singleRun,
handle h,
boolean transactionDistinct,
character exports,
String modes,
Object... args)
{
invokeExternalProcedure(name,
persistent,
singleton,
singleRun,
h,
transactionDistinct,
exports,
modes,
new ArrayArgumentResolver(args));
}
/**
* The implementation of all RUN ... PERSISTENT calls or appserver calls for an external
* procedure. If the <code>exports</code> parameter is not null, then the ON SERVER clause is
* in effect; this parameter can be not-null only on the remote, appserver, side.
*
* @param name
* The legacy 4GL name for the function or procedure.
* @param persistent
* Flag indicating that this call needs to be persistent.
* @param singleton
* Flag indicating that this call needs to be ran as singleton.
* @param singleRun
* Flag indicating that this call needs to be ran as single-run.
* @param h
* The handle where to save the persistent procedure.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param exports
* The list of allowed external procedures to be invoked with a RUN ... ON SERVER
* statement.
* @param modes
* A string representation of the modes of each parameter.
* @param argResolver
* The argument resolver.
*/
static void invokeExternalProcedure(character name,
boolean persistent,
boolean singleton,
boolean singleRun,
handle h,
boolean transactionDistinct,
character exports,
String modes,
ArgumentResolver argResolver)
{
WorkArea wa = work.obtain();
String pname = name.toStringMessage();
InternalEntryCaller caller = null;
Exception deferred = null;
try
{
caller = wa.externalResolver.resolve(null, pname, false, false, exports,
persistent);
if (caller == null)
{
if (!wa.em.isPending())
{
// not a pending error, it means the program was not found
invokeFailure(pname);
}
return;
}
Object[] args = argResolver.resolve(caller, pname, false);
if (!validArguments(wa, pname, caller, false, modes, args))
{
return;
}
Object instance = caller.getCallerInstance();
boolean error = false;
try
{
Runnable push = () ->
{
Class<?> def = instance.getClass();
wa.pm.pushCalleeInfo(def, false, instance, instance,
ProcedureManager.EXTERNAL_PROGRAM, pname,
false, persistent, singleton, singleRun);
};
caller.setPushWorker(push);
boolean delaySet = h != null && h.delayPersistentSet();
if (h != null && !delaySet)
{
// the procedure instance must be available to the legacy code, for example when
// the invoked caller calls back the call site
h.set(new ExternalProgramWrapper(instance));
}
caller.invoke(modes, args);
if (h != null && delaySet)
{
// the handle should be set to its old value while the called legacy code is executed,
// the resource change happens after the called procedure ends
h.set(new ExternalProgramWrapper(instance));
}
}
catch (Exception e)
{
error = true;
throw e;
}
finally
{
if (persistent)
{
if ((error || wa.em.isPending()))
{
// any exceptions here cause the persistent procedure to no longer
// be available
if (h != null)
{
h.setUnknown();
}
wa.pm.delete(instance, false);
}
else if (h != null)
{
// if the handle is not also passed as an OUTPUT argument, then assign it
boolean asArg = false;
String argModes = caller.getParameterModes(false);
int i = 0;
for (Object arg : args)
{
if (argModes == null || i >= argModes.length())
{
break;
}
char mode = argModes.charAt(i);
if (arg == h && mode != 'I')
{
// not an input mode, set it to unknown
asArg = true;
break;
}
i++;
}
if (asArg)
{
h.setUnknown();
}
}
}
}
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
throw ce;
}
// handle reflection exceptions
catch (NoSuchMethodException nsm)
{
invokeFailure(caller.getMethodName());
return;
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
// all other cases
catch (Exception exp)
{
deferred = exp;
}
if (deferred != null)
{
invokeError(caller, pname, deferred);
}
}
/**
* Resolve the program handle of an internal procedure <code>name</code>.
* <p>
* The method will try to find the internal procedure <code>name</code> in program
* <code>h</code>. If <code>name</code> is not found in <code>h</code>, super procedures of
* <code>h</code> are searched. If <code>name</code> is not found in super procedures of
* <code>h</code> <code>SESSION</code> super procedures are searched. If <code>name</code>
* is not found, <code>null</code> is returned.
*
* @param h
* The program handle.
* @param name
* The internal procedure name.
*
* @return Program handle or <code>null</code> if not found.
*/
static handle resolveProgramForProcedure(handle h, String name)
{
WorkArea wa = work.obtain();
InternalEntryCaller caller = null;
ArrayList<Resolver> inHandleResolvers = wa.inHandleResolvers;
for (int i = 0; i < inHandleResolvers.size(); i++)
{
Resolver r = inHandleResolvers.get(i);
caller = r.resolve(h, name, false, false, null);
if (caller != null)
{
return caller.phandle;
}
}
return null;
}
/**
* Given a deferred program configuration, resolve its singleton or single-run instance,
* depending on how the original RUN statement (or REST service) was configured. This will
* either create a new class (if no singleton exists at this time or the mode is single-run),
* or reuse an instance via {@link ProcedureManager#getSingleton(Class)}.
*
* @param deferred
* The deferred program configuration.
* @param isClass
* Flag indicating if this instance must be a legacy 4GL class.
*
* @return A {@link handle} referencing either a {@link ObjectResource} or an
* {@link ExternalProgramWrapper}.
*/
public static handle resolveInstance(DeferredProgram deferred, boolean isClass)
{
if (isClass)
{
Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(deferred.getName());
if (cls == null)
{
invokeFailure(deferred.getName());
return null;
}
_BaseObject_ instance = deferred.isSingleRun()
? null
: (_BaseObject_) ProcedureManager.getSingleton(cls);
if (instance == null)
{
object<?> obj = new ObjectVar<>(cls);
obj.assign(ObjectOps.newInstance(cls));
if (!obj._isValid())
{
invokeFailure(deferred.getName());
return null;
}
instance = obj.ref;
if (deferred.isSingleton())
{
ProcedureManager.addSingleton(cls, instance);
}
}
return new handle(ObjectOps.asResource(instance));
}
String extProgName = SourceNameMapper.normalizeLegacyName(deferred.getName());
String className = SourceNameMapper.convertNameToClass(extProgName);
if (className == null)
{
invokeFailure(deferred.getName());
return null;
}
Class<?> cls = ExternalProcedureCaller.classCache.get(className);
if (cls == null)
{
try
{
cls = Class.forName(className);
ExternalProcedureCaller.classCache.putIfAbsent(className, cls);
}
catch (ClassNotFoundException e)
{
cls = null;
invokeFailure(deferred.getName());
}
}
if (cls == null)
{
return null;
}
handle hres = new handle();
if (deferred.isSingleRun())
{
invokeExternalProcedure(new character(deferred.getName()),
true, false, true, hres,
false, null, null);
}
else
{
Object instance = ProcedureManager.getSingleton(cls);
if (instance == null)
{
invokeExternalProcedure(new character(deferred.getName()),
true, true, false, hres,
false, null, null);
}
else
{
hres.assign(new ExternalProgramWrapper(instance));
}
}
return hres;
}
/**
* Convert BaseDataType value to instance of given class. If value is <code>null</code>,
* returns <code>null</code>. If value is <code>unknown value</code>, creates a new instance
* of the given type that represents the <code>unknown value</code>, otherwise returns value
* casted to given type.
*
* @param cls
* Class.
* @param value
* Value.
*
* @return If value is <code>null</code>, returns <code>null</code>. If value is
* <code>unknown value</code>, creates a new instance of the given type that
* represents the <code>unknown value</code>, otherwise returns value casted to
* given type.
*
* @throws IllegalStateException
* If value is not compatible with given class.
*/
private static <T extends BaseDataType> T convertValue(Class<T> cls, BaseDataType value)
{
if (value == null)
{
return null;
}
// if we need to ensure that the value is of a specific data type, it means that
// we shouldn't use wrapper BDT proxy anymore and fire the implicit conversion
value = value.fallback();
if (value instanceof unknown)
{
value = BaseDataType.generateUnknown(cls);
}
if (!isValueCompatible(value, cls))
{
if (LOG.isLoggable(Level.WARNING))
{
LOG.warning("Value of type " + value.getClass().getName() +
" is not compatible with expected return type " + cls.getName());
}
}
return (T) value;
}
/**
* Test whether a value can be ultimately converted to a specific type/class.
*
* @param value
* The value to be tested.
* @param cls
* The expected type of the parameter. A subclass of {@code BaseDataType}.
* @param <T>
* The parameter type - a subclass of {@code BaseDataType}.
*
* @return {@code true} if the value is compatible with the specified parameter type.
*/
private static <T extends BaseDataType> boolean isValueCompatible(BaseDataType value, Class<T> cls)
{
// basic test which assures de facto compatibility
// value can be a proxy, so ensure that the cls is the one assignable from value
if (cls.isAssignableFrom(value.getClass()))
{
return true;
}
// all integer values can be assigned one to another because the assignment is performed using
// longValue() method. In a particular case, this avoid extra messages when the value is an object of
// an anonymous class overriding isAssignDirect() method.
if (int64.class.isAssignableFrom(cls) && value instanceof int64)
{
return true;
}
// IN SUPER may be defined with i.e. CHARACTER but actual definition may be LONGCHAR
// TODO: rewrite this condition as: 'Text.class.isAssignableFrom(cls) && value instanceof Text' ?
if (longchar.class == cls && value instanceof character ||
character.class == cls && value instanceof longchar)
{
return true;
}
// TODO: add other compatible types here
return false;
}
/**
* Execute an async procedure call.
*
* @param request
* The request which will execute the procedure call.
* @param hServer
* The server used to execute this async call.
* @param asyncHandle
* The handle to the asynchronous request.
* @param pHandle
* The handle to which the procedure belongs, if it is RUN ... IN handle statement.
*/
private static void invokeAsyncImpl(AsyncCall request,
handle hServer,
handle asyncHandle,
handle pHandle)
{
// expose the async resource to the caller
if (asyncHandle != null)
{
asyncHandle.assign(request.asyncReq);
}
boolean isServer = (hServer.getResource() instanceof ServerImpl);
ServerImpl server = isServer ? (ServerImpl) hServer.getResource() : null;
// register for notifications the server and the procedure handle
if (isServer)
{
request.asyncReq.registerListener(server);
}
// check the procedure handle
if (pHandle._isValid() && pHandle.getResource() instanceof AsyncRequestListener)
{
request.asyncReq.registerListener((AsyncRequestListener) pHandle.getResource());
}
// notify all listeners an async call is in effect
request.asyncReq.notifyStart();
// perform invocation. everything is executed on the Conversation thread until we reach the actual
// appserver invocation - when that is reached, we create an 'asyncAction' which will be ran on a
// separate thread
request.asyncReq.execute(isServer && server._connected());
}
/**
* The implementation for all function calls.
*
* @param name
* The legacy 4GL name for the function.
* @param h
* The handle to which the function belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a SUPER() call.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default or on error.
*/
private static BaseDataType invokeFunctionImpl(character name,
handle h,
boolean dynamicFunction,
boolean superCall,
String modes,
Object... args)
{
// TODO: add support for functions returning an extent
return invokeImpl((handle) null, h, name,
true, dynamicFunction, superCall,
false, modes, args);
}
/**
* The implementation for RUN SUPER and RUN ... IN handle calls.
*
* @param name
* The legacy 4GL name for the procedure.
* @param h
* The handle to which the procedure belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param superCall
* <code>true</code> if this is a RUN SUPER call.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The functions's arguments.
*/
private static void invokeInImpl(character name,
handle h,
boolean superCall,
String modes,
Object... args)
{
invokeImpl((handle) null, h, name, false, false, superCall, false, modes, args);
}
/**
* The implementation of function or procedure calls which do not use the
* IN handle clause.
*
* @param server
* The target appserver, to which this call will be dispatched.
* Is <code>null</code> if the RUN statement doesn't have the ON SERVER clause.
* @param name
* The legacy 4GL name for the function or procedure.
* @param h
* The handle to which the procedure belongs. If <code>null</code> defaults to
* THIS-PROCEDURE. If this is a proxy procedure, the call is dispatched to the
* procedure's server.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default, on error or for
* procedures.
*/
private static BaseDataType invokeImpl(handle server,
handle h,
character name,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
String modes,
Object... args)
{
return invokeImpl(null, server, h, name,
function, dynamicFunction, superCall, transactionDistinct,
modes, args);
}
/**
* The implementation of function or procedure calls which do not use the
* IN handle clause.
*
* @param asyncReq
* When not-null, an {@link AsyncRequestImpl} instance for the associated async
* request.
* @param server
* The target appserver, to which this call will be dispatched.
* Is <code>null</code> if the RUN statement doesn't have the ON SERVER clause.
* @param name
* The legacy 4GL name for the function or procedure.
* @param h
* The handle to which the procedure belongs. If <code>null</code> defaults to
* THIS-PROCEDURE. If this is a proxy procedure, the call is dispatched to the
* procedure's server.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The arguments.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default, on error or for
* procedures.
*/
private static BaseDataType invokeImpl(AsyncRequestImpl asyncReq,
handle server,
handle h,
character name,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
String modes,
Object... args)
{
// save the original received server instance.
final handle origServer = server;
DeferredProgram deferred = null;
if (server == null && h != null && h._isValid())
{
WrappedResource res = h.getResource();
// get the server from the handle
if (ProcedureManager.isProxy(h))
{
server = new handle(((ProxyProcedureWrapper) res).getServer());
}
else if (res instanceof PortTypeWrapper)
{
server = new handle(((PortTypeWrapper) res).getServer());
}
else if (res instanceof DeferredProgramWrapper)
{
deferred = (DeferredProgram) ((DeferredProgramWrapper) res).get();
server = deferred.getServer() == null ? null : new handle(deferred.getServer());
}
}
// if the server is the SESSION handle, then this is a local call
if (server == null || CompareOps._isEqual(server, SessionUtils.asHandle()))
{
if (transactionDistinct)
{
invalidHandle(name);
return unknown.UNKNOWN;
}
handle htarget = h;
if (deferred != null)
{
htarget = resolveInstance(deferred, false);
if (htarget == null || htarget.isUnknown())
{
return unknown.UNKNOWN;
}
}
try
{
// this is a normal call, invoke immediately
return invoke(htarget, name, function, dynamicFunction, superCall, false, modes, args);
}
finally
{
if (deferred != null && deferred.isSingleRun())
{
ProcedureManager.deleteInstance(htarget, false);
}
}
}
else
{
if (!validServer(server, h, name))
{
return unknown.UNKNOWN;
}
ServerImpl s = (ServerImpl) server.get();
boolean isWebService = s.isWebService();
if (isWebService && origServer != null && h == null)
{
// the target portHandle must always be specified in case of
// RUN port-type ON SERVER h-web-server statements.
// if is missing, the RUN statement is a no-op
return unknown.UNKNOWN;
}
BaseDataType result = unknown.UNKNOWN;
try
{
// if sync mode, no async requests must be running
if (asyncReq == null && s.hasAsyncRequests())
{
return unknown.UNKNOWN;
}
// invoke appServer
ServerHelper helper = s.getServerHelper();
InvokeConfig cfg = new InvokeConfig(name);
cfg.setAsynchronous(asyncReq != null)
.setFunction(function)
.setDynamicFunction(dynamicFunction)
.setSuperCall(superCall)
.setTransactionDistinct(transactionDistinct)
.setModes(modes)
.setArguments(args)
.setInHandle(h);
if (!isWebService)
{
if (deferred != null)
{
cfg.setSingleRun(deferred.isSingleRun())
.setSingleton(deferred.isSingleton());
}
// send the server:REQUEST
cfg.storeRequest(s.getRequestInfo());
if (asyncReq != null)
{
asyncReq.getRequestInfo().ref.initialize(s.getRequestInfo().ref, true);
}
// if ON SERVER is used (h is null), only external procedures can be invoked.
// this is managed by the remote side.
result = wrapAppServerCall(() -> helper.invoke(0, asyncReq, cfg), origServer);
if (!function && asyncReq == null && result != null)
{
// TODO: the return-value is set for web-service requests only if the web-service
// is configured and exported via Progress.
setReturnValue((character) result);
result = unknown.UNKNOWN;
}
}
else
{
// only web service requests
result = helper.invoke(0, asyncReq, name, h,
function, dynamicFunction, superCall,
transactionDistinct, modes, args);
}
}
catch (ConditionException e)
{
if ((e instanceof StopConditionException && server != null) ||
isWebService ||
!(e.getCause() instanceof NumberedException))
{
throw e;
}
ErrorManager.recordOrThrowError((NumberedException) e.getCause());
}
catch (Exception e)
{
LOG.severe("Exception thrown by executing the appserver invoke callable.", e);
}
return result;
}
}
/**
* The implementation of all function and procedure calls.
*
* @param resolvers
* The {@link Resolver} instances to use to resolve the internal
* entry to a Java method, based on the passed parameters.
* @param h
* The handle to which the procedure belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param cname
* The legacy 4GL name for the function.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param asThread
* This marks if "AS-THREAD" option is specified with the RUN stmt.
* @param ht
* When non-null, it will hold a reference to a resource which manages the new
* thread.
* @param modes
* A string representation of the modes of each parameter.
* @param argResolver
* The argument resolver.
*
* @return The return value from the block (or its nested blocks) which
* is the <code>unknown value</code> by default, on error or for
* procedures.
*/
private static BaseDataType invokeImpl(List<Resolver> resolvers,
handle h,
character cname,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
boolean asThread,
handle ht,
String modes,
ArgumentResolver argResolver)
{
WorkArea wa = work.obtain();
BaseDataType result = unknown.UNKNOWN;
String name = cname.toStringMessage();
// handle validation is done first
boolean explicit = h != null;
if (!explicit)
{
h = wa.pm.thisProcedure();
}
// this is a special case only for functions. if the handle has been
// deleted and it still contains the reference to the procedure, then
// it defaults to this-procedure
if (function && !h._isValid() && ProcedureManager.isProcedure(h))
{
h = wa.pm.thisProcedure();
}
if (!validHandle(wa, h, name, explicit, function, dynamicFunction))
{
return result;
}
if (cname.isUnknown() || (!explicit && cname.toStringMessage().length() == 0))
{
String err = null;
int errid = -1;
if (function && dynamicFunction)
{
err = "Could not invoke dynamic user-defined function";
errid = 5640;
}
if (err == null)
{
if (cname.isUnknown())
{
invokeFailure(name);
return result;
}
else
{
err = "No PROGRESS program specified for RUN or COMPILE";
errid = 467;
}
}
ErrorManager.recordOrThrowError(errid, err);
return result;
}
if (explicit && cname.toStringMessage().length() == 0)
{
String pname = wa.pm.getRelativeName(h.get());
String msg = "Procedure " + pname + " has no entry point for " + cname.toStringMessage();
ErrorManager.recordOrThrowError(6456, msg, false);
return result;
}
Exception deferred = null;
InternalEntryCaller caller = null;
try
{
for (Resolver r : resolvers)
{
caller = r.resolve(h, name, function, superCall, null);
if (caller instanceof MapToNotFound)
{
// if a map-to was searched and not explicitly found, search in super-procedures
name = caller.getInternalEntryName();
caller = null;
}
if (caller != null)
{
break;
}
if (r instanceof ExternalProgramResolver && wa.em.isPending())
{
// instantiation failed (i.e. due to a SHARED problem), do nothing
return result;
}
}
if (caller == null)
{
String pname = wa.pm.getRelativeName(h.get());
if (function)
{
// if the function is defined but could not be resolved,
// then show error
if (dynamicFunction)
{
final String msg =
"User-defined function '%s' invoked dynamically but could not be found";
if (SessionUtils._isRemote() && wa.tm.isGlobalBlock())
{
ErrorManager.recordOrThrowError(5639, String.format(msg, name), false);
}
else
{
ErrorManager.recordOrShowError(5639, String.format(msg, name),
false, false, false);
}
}
else
{
if (SessionUtils._isRemote() && wa.tm.isGlobalBlock())
{
final String msg =
"User-defined function '%s' invoked but could not be found";
ErrorManager.recordOrThrowError(6445, String.format(msg, name), false);
}
else
{
final String msg = "External user defined function '%s' is not defined in " +
"the procedure context given";
ErrorManager.recordOrShowError(2779, String.format(msg, name),
false, false, false);
}
}
return result;
}
else if (explicit)
{
final String msg = "Procedure " + pname + " has no entry point for " + name;
ErrorManager.recordOrThrowError(6456, msg, false);
return result;
}
invokeFailure(name);
return result;
}
if (function)
{
// caller.phandle is null in cases of external programs
handle callerHandle = caller != null ? caller.getPhandle() : null;
boolean sameHandles = callerHandle != null &&
explicit &&
h != null &&
CompareOps._isEqual(callerHandle, h);
if (callerHandle != null && !sameHandles)
{
// TODO: this should recursively call invokeImpl(server, handle, ....), as the
// new handle might be for a remote procedure
// handle has switched, validate the new one. dynamic-function
// needs to be false here.
if (!validHandle(wa, callerHandle, name, explicit, function, false))
{
return result;
}
}
handle funcProg = callerHandle != null ? callerHandle : h;
// this recursive call is needed only for functions, as only they
// can be set as IN handle.
String progName = ProcedureManager.getAbsoluteName(funcProg.get());
if (!sameHandles && SourceNameMapper.isInHandle(progName, caller.ie, name, function))
{
try
{
return invokeImpl(resolvers,
funcProg,
cname,
function,
dynamicFunction,
superCall,
transactionDistinct,
asThread,
ht,
modes,
argResolver);
}
catch (StackOverflowError t)
{
final String msg = "Giving up search for external user defined function " +
"'%s' after 64 indirections";
String err = String.format(msg, name);
int num = 2757;
ErrorManager.displayError(num, err, false);
return result;
}
}
}
Object[] args = argResolver.resolve(caller, name, function);
if (!validArguments(wa, name, caller, function, modes, args))
{
return result;
}
handle hTarget;
boolean external = (caller instanceof ExternalProcedureCaller);
final String iename;
final String pname;
if (external)
{
// we are invoking an external program. the target-procedure will be the
// to-be-invoked procedure.
ExternalProgramWrapper extProg = new ExternalProgramWrapper(caller.callerInstance);
hTarget = new handle(extProg);
iename = ProcedureManager.EXTERNAL_PROGRAM;
pname = name;
}
else
{
// use the original program handle as the target, consider
// the case when the internal procedure is resolved from
// the SUPER program
hTarget = new handle(h);
iename = name;
pname = null;
if (TransactionImpl.isTxInitProcedure(caller.callerInstance) &&
!(TransactionImpl.isOpenByTxInitProc() ||
wa.tm.getNestingLevel() == 2 ||
TransactionImpl.isTxInitProcedure(wa.pm._thisProcedure())))
{
// if the target is the TX:TRANS-INIT-PROCEDURE, then we can call this only if we
// are TX:IS-OPEN (by TRANS-INIT-PROCEDURE) or THIS-PROCEDURE is
// TX:TRANS-INIT-PROCEDURE
ErrorManager.recordOrThrowError(7337,
"Must run methods or internal procedures of a " +
"TRANSACTION-MODE AUTOMATIC .p remotely as top " +
"level procedures on an application server",
false);
return result;
}
}
Object callerInstance = caller.callerInstance;
Object[] res = new Object[1];
Exception[] exc = new Exception[1];
InternalEntryCaller[] acaller = new InternalEntryCaller[1];
acaller[0] = caller;
Runnable worker = () ->
{
WorkArea localWa = work.obtain();
acaller[0].wa = localWa;
Runnable push = () ->
{
// need to use my own workarea instance
Class<?> def = callerInstance.getClass();
localWa.pm.pushCalleeInfo(def, superCall, hTarget.get(), callerInstance,
iename, pname, function, false, dynamicFunction);
if (acaller[0].ie != null)
{
localWa.pm.peekCalleeInfo().setLegacyName(acaller[0].ie.pname);
}
};
acaller[0].setPushWorker(push);
try
{
res[0] = acaller[0].invoke(modes, args);
}
catch (Exception t)
{
exc[0] = t;
}
};
if (asThread)
{
// exceptions or returned value are of no use in the caller's thread
runAsThread(ht, worker);
}
else
{
worker.run();
if (exc[0] != null)
{
if (exc[0] instanceof ConditionException)
{
throw (ConditionException) exc[0];
}
else if (exc[0] instanceof RetryUnwindException)
{
throw (RetryUnwindException) exc[0];
}
else if (exc[0] instanceof NoSuchMethodException)
{
throw (NoSuchMethodException) exc[0];
}
else if (exc[0] instanceof InvocationTargetException)
{
throw (InvocationTargetException) exc[0];
}
else if (exc[0] instanceof Exception)
{
throw (Exception) exc[0];
}
else
{
throw new RuntimeException(exc[0]);
}
}
result = checkInvokeResult(res[0], name, function, dynamicFunction);
}
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
throw ce;
}
// handle reflection exceptions
catch (NoSuchMethodException nsm)
{
// should never happen, unless name_map.xml is corrupt
deferred = nsm;
}
catch (InvocationTargetException exc)
{
deferred = checkInvocationException(exc);
}
// all other cases
catch (Exception exp)
{
deferred = exp;
}
if (deferred != null)
{
invokeError(caller, name, deferred);
}
return result;
}
/**
* The implementation for the RUN ... PERSISTENT [SET handle] [ON SERVER server] statement.
*
* @param server
* The server instance on which to run the procedure. This will save the procedure
* in the remote persistent procedure list, not on the requester side. May be null.
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle to which the procedure reference must be saved.
* When <code>null</code> and not ran remotely, just adds the procedure to the
* persistent procedure list.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
private static void invokePersistentImpl(handle server,
character name,
handle persistHandle,
handle portHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
invokePersistentImpl(null, server, name,
persistHandle, portHandle,
transactionDistinct,
modes, args);
}
/**
* Initialize the proxy procedure associated with the async request.
*
* @param call
* The async request which currently holds a not-initialized proxy procedure.
*/
private static void initializeProxy(AsyncCall call)
{
if (!validServer(call.hServer, call.pHandle, call.pName))
{
return;
}
ServerImpl s = (ServerImpl) call.hServer.get();
if (s.isWebService())
{
throw new IllegalStateException(
"initializeProxy can be used only with an appserver connection!");
}
else
{
try
{
// execute the proxy
AppServerHelper appServer = (AppServerHelper) s.getServerHelper();
appServer.initializeProxy(call.asyncReq,
(ProxyProcedureWrapper) call.pHandle.getResource(),
call.transactionDistinct,
call.asyncReq.getModes(),
call.asyncReq.getArgs());
}
catch (ConditionException e)
{
if (e.getCause() instanceof NumberedException)
{
ErrorManager.recordOrThrowError((NumberedException) e.getCause());
}
else
{
throw e;
}
}
}
}
/**
* Invoke the specified external program in either SINGLETON or SINGLE-RUN modes.
* <p>
* This just creates a {@link DeferredProgram} configuration, saving the target name and any
* remote server configuration specified at the RUN statement.
*
* @param singleRun
* Flag indicating if we are using SINGLE-RUN (if <code>true</code>);
* <code>false</code> for SINGLETON.
* @param name
* The external program name.
* @param hproc
* The handle where to save this deferred resource.
* @param server
* A server configuration - may be unknown or <code>null</code>.
* @param transactionDistinct
* Flag indicating if the TRANSACTION-DISTINCT option was used.
*/
private static void invokeSingletonOrSingleRun(boolean singleRun,
character name,
handle hproc,
handle server,
boolean transactionDistinct)
{
// if the server is the SESSION handle, then this is a local call
if (server == null || CompareOps._isEqual(server, SessionUtils.asHandle()))
{
if (transactionDistinct)
{
invalidHandle(name);
return;
}
// make it a local call
server = null;
}
DeferredProgram deferred = null;
if (server != null)
{
if (!validServer(server, null, name))
{
return;
}
ServerImpl s = (ServerImpl) server.get();
if (s.isWebService())
{
final String err =
"The " + (singleRun ? "SINGLE-RUN" : "SINGLETON") +
" keyword is invalid when used with a SERVER handle whose " +
"SUBTYPE is 'WEBSERVICE'";
ErrorManager.recordOrThrowError(singleRun ? 17217 : 17215, err, false);
return;
}
deferred = new DeferredProgram(name.toStringMessage(), s, transactionDistinct);
}
else
{
deferred = new DeferredProgram(name.toStringMessage());
}
deferred.setSingleRun(singleRun);
deferred.setSingleton(!singleRun);
WorkArea wa = work.obtain();
// no special type is required - just create an unique instance, which will identify this
// 'proxy' program
handle hres = new handle();
hres.assign(new DeferredProgramWrapper(deferred));
wa.pm.addProcedure(hres, name.toStringMessage(), !singleRun, singleRun);
if (hproc != null)
{
hproc.assign(hres);
}
}
/**
* The implementation for the RUN ... PERSISTENT [SET handle] [ON SERVER server] statement.
*
* @param asyncReq
* When not-null, an {@link AsyncRequestImpl} instance for the associated async
* request.
* @param server
* The server instance on which to run the procedure. This will save the procedure
* in the remote persistent procedure list, not on the requester side. May be null.
* @param name
* The legacy 4GL name for the procedure.
* @param persistHandle
* The handle to which the procedure reference must be saved.
* When <code>null</code> and not ran remotely, just adds the procedure to the
* persistent procedure list.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
private static void invokePersistentImpl(AsyncRequestImpl asyncReq,
handle server,
character name,
handle persistHandle,
handle portHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
// this will always be an external procedure call
if (name.isUnknown() || name.toStringMessage().length() == 0)
{
String err = "No PROGRESS program specified for RUN or COMPILE";
ErrorManager.recordOrThrowError(467, err);
return;
}
// if the server is the SESSION handle, then this is a local call
if (server == null || CompareOps._isEqual(server, SessionUtils.asHandle()))
{
if (transactionDistinct)
{
invalidHandle(name);
return;
}
// this is a normal call, invoke immediately
invokeExternalProcedure(name, true, persistHandle, false, null, modes, args);
}
else
{
// validate server first
if (!validServer(server, null, name))
{
return;
}
ServerImpl s = (ServerImpl) server.get();
if (s.isWebService())
{
if (portHandle == null)
{
final String err =
"The PERSISTENT keyword is invalid when used with a SERVER handle whose " +
"SUBTYPE is 'WEBSERVICE'";
ErrorManager.recordOrThrowError(11481, err, false);
return;
}
if (asyncReq != null)
{
final String err =
"Unexpected Web service client error. Copy the following message and contact " +
"technical support. Web service procedure not initialized in cwsSend";
ErrorManager.recordOrThrowError(11461, err, false);
return;
}
// this will establish a port connection
WebServiceHelper helper = (WebServiceHelper) s.getServerHelper();
PersistentProcedure portProcedure = helper.runPort(name);
if (portProcedure != null)
{
// the port was ran, assign it
portHandle.assign(portProcedure);
// this is not a persistent procedure, and does not get registered with the
// ProcedureManager
}
return;
}
else
{
if (portHandle != null)
{
final String err =
"'RUN ... ON SERVER server-handle' with the [SET handle] option is not valid " +
"without the PERSISTENT keyword unless the server is a Web service";
ErrorManager.recordOrThrowError(14587, err, false);
return;
}
// if sync mode, no async requests must be running
if (asyncReq == null && s.hasAsyncRequests())
{
return;
}
ProxyProcedureWrapper proxyProcedure = null;
handle h = persistHandle;
AppServerHelper appServer = (AppServerHelper) s.getServerHelper();
try
{
if (h == null)
{
h = new handle();
}
InvokeConfig cfg = new InvokeConfig(name);
cfg.setAsynchronous(asyncReq != null)
.setProcedureHandle(h)
.setTransactionDistinct(transactionDistinct)
.setModes(modes)
.setArguments(args);
// send the server:REQUEST
cfg.storeRequest(s.getRequestInfo());
if (asyncReq != null)
{
asyncReq.getRequestInfo().ref.initialize(s.getRequestInfo().ref, true);
}
BaseDataType result = wrapAppServerCall(() -> appServer.invoke(0, asyncReq, cfg), cfg.getServerHandle());
if (asyncReq == null && result != null)
{
setReturnValue(new character(result));
}
proxyProcedure = (ProxyProcedureWrapper) h.getResource();
if (persistHandle != null)
{
persistHandle.set(proxyProcedure);
}
proxyProcedure.setServer(s);
}
catch (ConditionException e)
{
if (e.getCause() instanceof NumberedException)
{
ErrorManager.recordOrThrowError((NumberedException) e.getCause());
}
else
{
throw e;
}
}
catch (Exception e)
{
LOG.severe("Exception thrown by executing the appserver invoke callable.", e);
}
// chain the procedure
handle last = s.lastProcedure();
if (last.isUnknown())
{
s.setFirstProcedure(proxyProcedure);
s.setLastProcedure(proxyProcedure);
}
else
{
ProxyProcedureWrapper lr = (ProxyProcedureWrapper) last.getResource();
lr.setNextSibling(proxyProcedure);
proxyProcedure.setPrevSibling(lr);
s.setLastProcedure(proxyProcedure);
}
}
}
}
/**
* Saves the current list of client types and sets the new values from the server connect type. Executes
* the appserver call and reverts the client types. Handles QUIT condition thrown by the appserver call.
*
* @param serverCall
* The appserver call.
* @param serverHandle
* The server handle.
*
* @return The result of the appserver call.
*
* @throws Exception
* Exception thrown by the callable.
*/
private static BaseDataType wrapAppServerCall(Callable<BaseDataType> serverCall, handle serverHandle)
throws Exception
{
ClientType[] origClientTypes = SessionUtils.getClientTypes();
if (serverHandle != null && serverHandle.getResource() instanceof ServerImpl)
{
ClientType[] newClientTypes = ((ServerImpl) serverHandle.getResource()).getClientTypes();
SessionUtils.setClientTypes(newClientTypes);
}
try
{
return serverCall.call();
}
catch (QuitConditionException e)
{
if (serverHandle == null)
{
// don't propagate to the outer procedure QUIT executed on an appserver
throw e;
}
}
finally
{
if (serverHandle != null)
{
SessionUtils.setClientTypes(origClientTypes);
}
}
return null;
}
/**
* Check if the caller can validate the arguments. If not, show an appropriate error message
* and throw error condition.
* <p>
* Note that the content of the {@code args} array can be changed. An item will be changed if
* any of following occurs:
* <ul>
* <li>variables are replaced in O/U modes with local variables
* <li>handle access discrepancies
* <li>types of parameters do not match but they are compatible
* </ul>
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param name
* The legacy program name.
* @param caller
* The caller used to validate the arguments.
* @param function
* <code>true</code> if this is a function call.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The argument list.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validArguments(WorkArea wa,
String name,
InternalEntryCaller caller,
boolean function,
String modes,
Object... args)
{
boolean res = false;
try
{
res = validArgumentsInt(wa, name, caller, function, modes, args);
}
finally
{
if (!res)
{
cleanupPending(wa);
}
}
return res;
}
/**
* Check if the caller can validate the arguments. If not, show an appropriate error message
* and throw error condition.
* <p>
* Note that the content of the {@code args} array can be changed. An item will be changed if
* any of following occurs:
* <ul>
* <li>variables are replaced in O/U modes with local variables
* <li>handle access discrepancies
* <li>types of parameters do not match but they are compatible
* </ul>
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param name
* The legacy program name.
* @param caller
* The caller used to validate the arguments.
* @param function
* <code>true</code> if this is a function call.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The argument list.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validArgumentsInt(WorkArea wa,
String name,
InternalEntryCaller caller,
boolean function,
String modes,
Object... args)
{
wa.invalidArgs = false;
int[] extentInfo = new int[2]; // [0] = argument extent, [1] = parameter (expected) extent
ArgValidationErrors argsValid = caller.valid(function, modes, extentInfo, args);
// deferred validation needs no further processing here
if (argsValid == ArgValidationErrors.DEFER_VALIDATION)
{
return true;
}
if (caller.iename != null)
{
// we have an internal entry, determine the relative name used to start this referent
name = wa.pm.getRelativeName(caller.callerInstance);
}
if (argsValid == ArgValidationErrors.NO_ERROR)
{
// validate the parameter's modes
if (modes != null && !modes.isEmpty())
{
String definedModes = caller.getParameterModes(function);
if (!modes.equals(definedModes))
{
wa.invalidArgs = true;
final String errp =
"Mismatched parameters types passed to procedure %s %s";
final String errf =
"Mismatched parameter INPUT/OUTPUT mode passed to routine %s %s";
String err = function ? errf : errp;
int errno = function ? 6349 : 3230;
String msg = String.format(err, caller.getInternalEntryName(), name);
ErrorManager.recordOrThrowError(errno, msg, false);
return false;
}
}
return true;
}
wa.invalidArgs = true;
String[] errs = null;
int[] errIds = null;
String callingProcedure =
SessionUtils._isRemote()
? "(Remote client)"
: wa.pm.getStackEntry(0);
String invocationTarget =
wa != null && wa.currentInvoke != null
? wa.currentInvoke.getTarget()
: "";
invocationTarget =
invocationTarget.equals("") || invocationTarget == null || invocationTarget.equals(name)
? ""
: ":" + invocationTarget;
switch (argsValid)
{
case UNEXPECTED_PARAM:
errIds = new int[] { 1005 };
String msg = "Procedure %s passed parameters to %s%s, which did not expect any";
msg = String.format(msg, callingProcedure, name, invocationTarget);
errs = new String[] { msg };
break;
case INCORRECT_COUNT:
errIds = new int[] { 3234 };
msg = "Mismatched number of parameters passed to routine %s %s%s";
msg = String.format(msg, caller.getInternalEntryName(), name, invocationTarget);
errs = new String[] { msg };
break;
case INCORRECT_TYPES:
errIds = new int[] { 5729, 2570 };
msg = "Routine %s sent called routine %s %s%s mismatched parameters";
msg = String.format(msg,
callingProcedure,
caller.getInternalEntryName(),
name,
invocationTarget);
errs = new String[]
{
"Incompatible datatypes found during runtime conversion",
msg
};
break;
case ARRAY_FOR_NON_ARRAY:
case NON_ARRAY_FOR_ARRAY:
case BAD_ARRAY_DIMENSION:
errIds = new int[] { 11428 };
// TODO: msg doesn't end in DOT in 4GL
msg = "Extent parameter dimension of %d from procedure %s is mismatched with " +
"sub-procedure %s %s%s parameter dimension of %d ...(328)";
msg = String.format(msg,
extentInfo[0],
callingProcedure,
caller.getInternalEntryName(),
name,
invocationTarget,
extentInfo[1]);
errs = new String[] { msg };
break;
case DYN_ARRAY_FOR_NON_ARRAY:
errIds = new int[] { 12290 };
msg = "Calling procedure %s cannot pass an indeterminate extent parameter if the " +
"sub-procedure %s %s%s parameter is a non-array parameter";
msg = String.format(msg,
callingProcedure,
caller.getInternalEntryName(),
name,
invocationTarget);
errs = new String[] { msg };
break;
case NON_ARRAY_FOR_DYN_ARRAY:
errIds = new int[] { 15472 };
msg =
"Routine %s passed a non-array parameter to an array parameter of routine %s %s%s";
msg = String.format(msg,
callingProcedure,
caller.getInternalEntryName(),
name,
invocationTarget);
errs = new String[] { msg };
break;
}
ErrorManager.recordOrThrowError(errIds, errs, false);
return false;
}
/**
* Check if the handle is valid for function or procedure invocation.
* If is invalid, show an appropriate message.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param h
* The handle to which the procedure belongs. If <code>null</code>
* defaults to THIS-PROCEDURE.
* @param name
* The legacy 4GL name for the function.
* @param explicit
* <code>true</code> if the passed handle was explicitly set (for
* RUN ... IN or FUNCTION ... IN handle cases.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
*
* @return <code>true</code> if the handle is valid for function or
* procedure invocation.
*/
private static boolean validHandle(WorkArea wa,
handle h,
String name,
boolean explicit,
boolean function,
boolean dynamicFunction)
{
if (function)
{
boolean isProcedure = ProcedureManager.isProcedure(h);
if (h.isUnknown() || !isProcedure)
{
String err = null;
int num = -1;
if (!h.isUnknown() && !isProcedure)
{
final String msg =
"Could not locate external function '%s'. The handle to " +
"the procedure context is invalid";
err = String.format(msg, name);
num = 2777;
}
else if (explicit && dynamicFunction)
{
err = "DYNAMIC-FUNCTION function given invalid or Unknown context to run IN";
num = 5705;
}
else
{
final String msg =
"Could not evaluate the expression describing the " +
"context of external function '%s'";
err = String.format(msg, name);
num = 2767;
}
ErrorManager.recordOrThrowError(num, err, false);
return false;
}
}
else
{
if (explicit && h.isUnknown())
{
String err = null;
int num = -1;
final String msg =
"Could not evaluate procedure handle expression while trying to execute %s";
err = String.format(msg, name);
num = 2125;
ErrorManager.recordOrThrowError(num, err, false);
return false;
}
if (!wa.pm.hasReferent(h.get()))
{
String err = null;
int num = -1;
final String msg = "Invalid or inappropriate handle value given to RUN...IN " +
"statement. Procedure '%s':%d";
num = 2128;
handle pthis = wa.pm.thisProcedure();
String thisName = (pthis != null) ? wa.pm.getRelativeName(pthis.get()) : "null";
err = String.format(msg, thisName, 0);
ErrorManager.recordOrThrowError(num, err, false);
return false;
}
}
return true;
}
/**
* Check if the given handle is a valid, connected appserver.
*
* @param hServer
* The handle to validate as an appserver.
* @param hProc
* A procedure handle for IN handle cases.
* @param name
* The target 4GL legacy name.
*
* @return <code>true</code> if the given handle is valid.
*/
private static boolean validServer(handle hServer, handle hProc, character name)
{
final String err2 =
"Invalid or inappropriate server handle specified for RUN %s ... ON SERVER statement";
final int code2 = 5453;
if (hServer.isUnknown())
{
ErrorManager.recordOrThrowError(code2, err2, false);
return false;
}
Object resource = hServer.get();
if (!(resource instanceof ServerImpl))
{
final String err =
"The handle for \"RUN ... ON SERVER\" must be either a SERVER handle or " +
"a SESSION handle";
ErrorManager.recordOrThrowError(8998, err, false);
return false;
}
ServerImpl server = (ServerImpl) resource;
if (!server._connected())
{
final String err1 = "SERVER is not connected";
final int code1 = 5451;
int[] codes = null;
String[] msgs = null;
if (hProc != null && hProc._isValid() &&
(ProcedureManager.isProxy(hProc) || hProc.get() instanceof PortTypeWrapper))
{
codes = new int[] { code1 };
msgs = new String[] { err1 };
}
else
{
codes = new int[] { code1, code2 };
msgs = new String[] { err1, String.format(err2, name.toStringMessage()) };
}
ErrorManager.recordOrThrowError(codes, msgs, false);
return false;
}
return true;
}
/**
* Common helper to display a standard error message and then throw a
* STOP condition when the target procedure cannot be found. Silent
* error mode has no effect on this since this is not an ERROR condition.
* That means that this method never returns normally AND that the
* ERROR-STATUS data is unchanged.
*
* @param target
* The procedure that cannot be found.
*/
private static void invokeFailure(String target)
throws StopConditionException
{
int errCode = EXIT_CODE_PROCEDURE_NOT_FOUND;
String spec = "\"%s\" was not found";
String errMsg = String.format(spec, target);
String err = ErrorManager.buildErrorText(errCode, errMsg, true);
// RUN external.p should never be a silent error
boolean isSilentOriginally = ErrorManager.isSilent();
ErrorManager.setSilent(false);
ErrorManager.addError(errCode, errMsg, false, true);
ErrorManager.displayError(errCode, errMsg, true);
ErrorManager.setSilent(isSilentOriginally);
throw new StopConditionException(err, new NumberedException(err, errCode));
}
/**
* Common helper to generate error 5453 and then throw an error there is an invalid handle.
*
* @param name
* The RUN statement target.
*/
private static void invalidHandle(character name)
throws ErrorConditionException
{
ErrorManager.recordOrThrowError(5453,
"Invalid or inappropriate server handle specified " +
"for RUN " + name.toStringMessage() +
" ... ON SERVER statement",
false);
}
/**
* Throw a {@link RuntimeException}, as the function or procedure
* invocation has raised an exception.
*
* @param caller
* {@link InternalEntryCaller} caller instance used to invoke the
* function or procedure. May be null.
* @param name
* The legacy 4GL name passed to the function or procedure call
* statement.
* @param deferred
* The deferred error.
*/
private static void invokeError(InternalEntryCaller caller,
String name,
Exception deferred)
{
if (caller != null)
{
throw new RuntimeException("invoke() of class " +
caller.getClassName() +
" and method " +
caller.getMethodName() +
" failed",
deferred);
}
else
{
throw new RuntimeException("invoke() of program " +
name + " failed", deferred);
}
}
/**
* Run the specified code in a related thread.
*
* @param ht
* When non-null, it will save a resource which can manage the related thread doing
* this work.
* @param worker
* The work to be executed.
*/
private static void runAsThread(handle ht, Runnable worker)
{
// inherit batch mode, project token and stopdisp
String projectToken = Utils.getProjectToken();
logical batchMode = EnvironmentOps.isBatchMode();
int stopDisp = Utils.getDirectoryNodeInt(null, "stop_disposition", 1);
RelatedThread thread = new RelatedThread(() ->
{
try
{
// this is always in 'headless' mode
SecurityManager.getInstance().contextSm.setHeadless();
EnvironmentOps.setBatchMode(batchMode.booleanValue());
Utils.setProjectToken(projectToken);
// TODO: load project-specific search-path? this is done in StandardServer.standardEntry
// we may want to make this logic common (i.e. loading project-specific state).
StandardServer.invoke(stopDisp, new Isolatable()
{
@Override
public Object execute()
{
// TODO: for internal entries (or even external programs), the worker should
// check for @ThreadSafe annotation
worker.run();
return null;
}
});
}
catch (NullPointerException |
ReflectiveOperationException e)
{
throw new RuntimeException(e);
}
});
String currentThread = Thread.currentThread().getName();
thread.setName(currentThread + " - related");
// start the thread
thread.start();
if (ht != null)
{
// TODO: assign the handle to a resource which can manage this related thread
}
}
/**
* Override this native call to a FWD API call.
*
* @param phandle
* The procedure handle where this external entry is defined.
* @param libname
* The library name where this native procedure should be found.
* @param pname
* The legacy name of the external procedure from which this API is being called.
* @param iename
* The internal-entry name being called which is usually the same as the entry
* point name in the library except where an ordinal is being used.
* @param ie
* The already resolved internal entry.
*
* @return A non-null {@link InternalEntryCaller} instance, if this call can be overridden.
*/
private static InternalEntryCaller overrideNativeCall(handle phandle,
String libname,
String pname,
String iename,
InternalEntry ie)
{
// overrides lockWindowUpdate user32.dll calls to LogicalTerminal.disableRedraw
if (!iename.equalsIgnoreCase("lockWindowUpdate") ||
libname == null ||
libname.toLowerCase().indexOf("user32") < 0)
{
return null;
}
boolean missingReturn = ie.getParameterListSize() == 1;
if (!(ie instanceof NativeAPIEntry) ||
ie.isFunction() ||
(ie.getParameterListSize() != 2 && !missingReturn))
{
return null;
}
NativeAPIEntry calldef = (NativeAPIEntry) ie;
String pmodes;
Parameter pret = null;
Parameter phwnd = null;
if (missingReturn)
{
Parameter p0 = calldef.getParameter(0);
if (p0.getMode().equalsIgnoreCase("INPUT"))
{
pmodes = "I";
pret = null;
phwnd = p0;
}
else
{
return null;
}
}
else
{
Parameter p0 = calldef.getParameter(0);
Parameter p1 = calldef.getParameter(1);
if (p0.getMode().equalsIgnoreCase("INPUT") && p1.getMode().equalsIgnoreCase("RETURN"))
{
pmodes = "IO";
pret = p1;
phwnd = p0;
}
else if (p1.getMode().equalsIgnoreCase("INPUT") && p0.getMode().equalsIgnoreCase("RETURN"))
{
pmodes = "OI";
pret = p0;
phwnd = p1;
}
else
{
return null;
}
}
// HWND data types are LONG, UNSIGNED-LONG or INT64
switch (phwnd.getType())
{
case "LONG":
case "UNSIGNED-LONG":
case "INT64":
break;
default:
return null;
}
// RETURN data types are BYTE, SHORT, UNSIGNED-SHORT, LONG, UNSIGNED-LONG or INT64
if (!missingReturn)
{
switch (pret.getType())
{
case "BYTE":
case "SHORT":
case "UNSIGNED-SHORT":
case "LONG":
case "UNSIGNED-LONG":
case "INT64":
break;
default:
return null;
}
}
return new NativeProcedureCaller(work.obtain(), phandle, libname, pname, iename)
{
@Override
Object invokeImpl(String modes, Object... param) throws Exception
{
if (missingReturn)
{
if (param.length == 1 &&
(modes == null || pmodes.equals(modes)) &&
int64.class.isAssignableFrom(param[0].getClass()))
{
int64 hwnd = (int64) param[0];
LogicalTerminal.disableRedraw(new int64(hwnd));
return null;
}
}
else
{
if (param.length == 2 &&
int64.class.isAssignableFrom(param[0].getClass()) &&
int64.class.isAssignableFrom(param[1].getClass()))
{
int64 hwnd = null;
int64 ret = null;
if (modes == null)
{
modes = pmodes;
}
if (modes.equals("IO"))
{
hwnd = (int64) param[0];
ret = (int64) param[1];
}
else if (modes.equals("OI"))
{
ret = (int64) param[0];
hwnd = (int64) param[1];
}
if (hwnd != null && ret != null)
{
LogicalTerminal.disableRedraw(new int64(hwnd), ret);
return null;
}
}
}
ErrorManager.recordOrThrowError(0, "Could not invoke DISABLE-REDRAW!");
return null;
}
};
}
/**
* Cleanup all pending resources initialized by the external program default constructor, as the call
* failed.
*/
public static void cleanupPending()
{
cleanupPending(work.obtain());
}
/**
* Cleanup all pending resources initialized by the external program default constructor, as the call
* failed.
*/
private static void cleanupPending(WorkArea wa)
{
// cleanup all pending (to be registered) shared resources
wa.tm.cleanupPending();
SharedVariableManager.cleanupPending();
LogicalTerminal.cleanupPending();
wa.pm.cleanupPending();
BufferManager.cleanupPending();
}
/**
* Check the invocation result for a DYNAMIC-FUNCTION or FUNCTION call.
*
* @param res
* The returned value.
* @param name
* The function's name.
* @param function
* Flag identifying the call as a function call.
* @param dynamicFunction
* Flag identifying the call as a DYNAMIC-FUNCTION call.
*
* @return The prepared result.
*/
private static BaseDataType checkInvokeResult(Object res,
String name,
boolean function,
boolean dynamicFunction)
{
BaseDataType result = unknown.UNKNOWN;
if (res == null)
{
if (!function)
{
result = unknown.UNKNOWN;
}
else
{
final String msg = "Function call '%s' has returned a null value!";
throw new NullPointerException(String.format(msg, name));
}
}
else if (res instanceof BaseDataType)
{
result = (BaseDataType) res;
}
else
{
if (function)
{
final String spec =
"Incompatible return value by function %s: [%s] of type [%s]";
String err = String.format(spec, name,
result.toString(),
result.getClass().toString());
ErrorManager.displayError(err);
ErrorManager.handleStopException(err);
}
}
boolean isProxy = result != null && result.val() != result;
if (dynamicFunction)
{
if (result != null && !isProxy)
{
BaseDataType proxy = BaseDataType.createProxy(result);
if (result instanceof Text)
{
// preserve case sensitivity
((Text) proxy).setCaseSensitive(((Text) result).isCaseSensitive());
}
result = proxy;
}
}
else
{
if (isProxy)
{
result = result.fallback();
}
}
return result;
}
/**
* Check the exception cause in the chain of the exception returned from a reflection call, if it contains
* any FWD condition exceptions.
*
* @param exc
* The exception to check.
*
* @return The deferred exception, if the cause is not a {@link ConditionException}.
*
* @throws ConditionException
* @throws ErrorConditionException
* @throws RetryUnwindException
*/
private static Exception checkInvocationException(InvocationTargetException exc)
{
Exception deferred = null;
// if the exception thrown was a P2J runtime type, we
// must handle it normally, so extract the root cause
// exception and rethrow it
Throwable cause = exc.getCause();
while (cause != null)
{
if (cause instanceof ConditionException)
{
if (cause instanceof InvalidParameterConditionException)
{
// TODO: it is not entirely clear how safe it is to directly instantiate this
// error; the InvalidParameterConditionException can be raised (we think)
// only when there is some temp-table parameter error (see
// TemporaryBuffer.handleTablesDoNotMatch); the location for the throw
// suggests that the ERROR condition is raised regardless of NO-ERROR
// mode
// re-wrap NumberedException
throw new ErrorConditionException(cause.getCause());
}
else
{
throw (ConditionException) cause;
}
}
if (cause instanceof RetryUnwindException)
{
throw (RetryUnwindException) cause;
}
// exception is a runtime problem that we don't handle here
if (deferred == null)
{
deferred = exc;
}
cause = cause.getCause();
}
return deferred;
}
/**
* Cache the invocation target for a callsite.
* <p>
* The details about this invocation will be saved into a {@link LastInvokeDetails} instance, and cached
* in the {@link WorkArea#callSiteCache}, for the specified <code>thisCaller</code> (which can be either
* a {@link WrappedResource} or an external program instance) and {@link InvokeConfig#getCallSite() call-site}.
*
* @param wa
* The {@link WorkArea} instance.
* @param caller
* The caller for this invocation.
* @param cfg
* The invoke configuration.
* @param thisCaller
* The resource or referent performing this call, where the target will be cached.
* @param args
* The arguments for this call.
*/
private static void cacheInvocation(WorkArea wa,
InternalEntryCaller caller,
InvokeConfig cfg,
Object thisCaller,
Object[] args)
{
String target = cfg.getTarget();
String modes = cfg.getModes();
String eventProcedureName = cfg.getEventProcedure() == null
? null
: cfg.getEventProcedure().toStringMessage();
Class<?>[] argumentTypes = null;
Object eventProcedureContext = cfg.getEventProcedureContext() == null
? null
: cfg.getEventProcedureContext().get();
Object inHandle = cfg.getInHandle() == null ? null : cfg.getInHandle().get();
boolean externalProcedure = caller.getPhandle() == null;
Object instance = externalProcedure ? caller.getClassName() : caller.getCallerInstance();
Method mthd = externalProcedure ? null : caller.mthd;
if (args != null && args.length > 0)
{
argumentTypes = new Class[args.length];
for (int i = 0; i < args.length; i++)
{
argumentTypes[i] = args[i].getClass();
}
}
LastInvokeDetails invoke = new LastInvokeDetails((caller.ie == null ? null : caller.ie.pname),
target,
modes,
inHandle,
eventProcedureName,
eventProcedureContext,
instance,
mthd,
argumentTypes);
Map<InvokeConfig, LastInvokeDetails> thisCallSite =
externalProcedure ? wa.extProgcallSiteCache
: wa.callSiteCache.computeIfAbsent(thisCaller, (k) -> new IdentityHashMap<>());
thisCallSite.put(cfg.getCallSite(), invoke);
}
/**
* The cached details of an invocation callsite.
*/
private static class LastInvokeDetails
{
/** The legacy name of the target. */
private final String legacyName;
/** The exact target name used at the call-site. */
private final String target;
/** The parameter modes. */
private final String modes;
/** The event procedure name being invoked, in case of an ASYNC call. */
private final String eventProcedureName;
/** The argument types for this call. */
private final Class<?>[] argumentTypes;
/** The event procedure context, in case of an ASYNC call. */
private final WeakReference<Object> eventProcedureContext;
/** The target handle for an internal procedure, specified at the call. */
private final WeakReference<Object> inHandle;
/**
* The actual external program from where the {@link #target} was resolved and the {@link #method} is
* being invoked.
*/
private final WeakReference<Object> reference;
/** The Java method to invoke. */
private final Method method;
/**
* Create a new instance.
*
* @param legacyName
* The legacy name of the target.
* @param target
* The exact target name used at the call-site.
* @param modes
* The parameter modes.
* @param inHandle
* The target handle for an internal procedure, specified at the call.
* @param eventProcedureName
* The event procedure name being invoked, in case of an ASYNC call.
* @param eventProcedureContext
* The event procedure context, in case of an ASYNC call.
* @param reference
* The actual external program from where the {@link #target} was resolved and the
* {@link #method} is being invoked.
* @param method
* The Java method to invoke.
* @param argumentTypes
* The argument types for this call.
*/
public LastInvokeDetails(String legacyName,
String target,
String modes,
Object inHandle,
String eventProcedureName,
Object eventProcedureContext,
Object reference,
Method method,
Class<?>[] argumentTypes)
{
this.legacyName = legacyName;
this.target = target;
this.modes = modes;
this.eventProcedureName = eventProcedureName;
this.argumentTypes = argumentTypes;
this.eventProcedureContext = (eventProcedureContext == null
? null
: new WeakReference<>(eventProcedureContext));
this.inHandle = (inHandle == null ? null : new WeakReference<>(inHandle));
this.reference = (reference == null ? null : new WeakReference<>(reference));
this.method = method;
}
/**
* Check if this cached details are for an external program.
*
* @return <code>true</code> if {@link #method} is <code>null</code>.
*/
public boolean isExternalProgram()
{
return method == null;
}
/**
* Check if this invocation matches a function call.
*
* @param cfg
* The configuration to match against.
*
* @return <code>true</code> if <code>cfg</code> is for a matching function call.
*
* @see #matches(InvokeConfig)
*/
public boolean matchesFunction(InvokeConfig cfg)
{
if (!cfg.isFunction())
{
return false;
}
return matches(cfg);
}
/**
* Check if this invocation matches a RUN call.
* <p>
* The configuration matches if and only if:
* <ul>
* <li>{@link #matches(InvokeConfig)} is <code>true</code></li>
* <li>{@link InvokeConfig#getServerHandle()} is the SESSION handle, if specified.</li>
* <li>{@link InvokeConfig#getEventProcedure()} is the same as {@link #eventProcedureName},
* if specified.</li>
* <li>{@link InvokeConfig#getEventProcedureContext()} is the same as {@link #eventProcedureContext},
* if specified.</li>
* </ul>
*
* @param cfg
* The configuration to match against.
*
* @return <code>true</code> if <code>cfg</code> is for a matchin RUN call.
*
* @see #matches(InvokeConfig)
*/
public boolean matchesRun(InvokeConfig cfg)
{
if (cfg.isFunction() || cfg.isClass())
{
return false;
}
if (!matches(cfg))
{
return false;
}
if (cfg.getServerHandle() != null && !SessionUtils.asHandle().equals(cfg.getServerHandle()))
{
// not targeting SESSION
return false;
}
if (eventProcedureName != null)
{
if (cfg.getEventProcedure() == null ||
!eventProcedureName.equalsIgnoreCase(cfg.getEventProcedure().toStringMessage()))
{
// event procedure does not match
return false;
}
}
else if (cfg.getEventProcedure() != null)
{
return false;
}
if (eventProcedureContext != null)
{
Object lastContext = eventProcedureContext.get();
handle evtContext = cfg.getEventProcedureContext();
if (evtContext != null && !evtContext._isValid())
{
// the target is not valid, let it fail
return false;
}
Object cfgContext = (evtContext != null ? evtContext.get() : null);
if (lastContext != cfgContext)
{
// target has changed
return false;
}
}
else if (cfg.getEventProcedureContext() != null)
{
return false;
}
return true;
}
/**
* Check if this invocation matches the specified configuration.
* <p>
* The configuration matches if and only if:
* <ul>
* <li>{@link #reference} is set and still valid.</li>
* <li>{@link InvokeConfig#getTarget()} is the same as {@link #target}.</li>
* <li>{@link InvokeConfig#getModes()} is the same as {@link #modes}, if specified.</li>
* <li>{@link InvokeConfig#getInHandle()} is the same as {@link #inHandle}, if specified.</li>
* <li>{@link InvokeConfig#getArguments()} types are the same as {@link #argumentTypes},
* if specified.
* </li>
* </ul>
*
*
* @param cfg
* The configuration to match against.
*
* @return <code>true</code> if <code>cfg</code> matches.
*/
private boolean matches(InvokeConfig cfg)
{
if (!validReference())
{
return false;
}
if (cfg.getTarget() == null ||
cfg.getTarget().isEmpty() ||
!target.equalsIgnoreCase(cfg.getTarget()))
{
// target does not match
return false;
}
if (!((cfg.getModes() == null && modes == null) ||
(cfg.getModes() != null && cfg.getModes().equals(modes))))
{
return false;
}
if (inHandle != null)
{
Object lastIn = inHandle.get();
handle in = cfg.getInHandle();
if (in != null && !in._isValid())
{
// the target is not valid, let if fail
return false;
}
Object inRef = (in != null ? in.get() : null);
if (lastIn != inRef)
{
// target has changed
return false;
}
}
else if (cfg.getInHandle() != null && !cfg.getInHandle()._isValid())
{
// if specified, IN handle must be valid
return false;
}
Object[] args = cfg.getArguments();
if (args != null && args.length == 0)
{
args = null;
}
if (!((args == null && argumentTypes == null) ||
(args != null && argumentTypes != null && args.length == argumentTypes.length)))
{
// arguments must be either not set or the same length, otherwise don't use cached target
return false;
}
// check argument types
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
Class<?> type = (args[i] == null ? null : args[i].getClass());
if (type != argumentTypes[i] || type == null || argumentTypes[i] == null)
{
// the signature does not match
return false;
}
}
}
return true;
}
/**
* Check if the {@link #reference} still exists.
*
* @return See above.
*/
private boolean validReference()
{
return reference == null || reference.get() != null;
}
}
/**
* Transform any {@link DataSetParameter} or {@link TableParameter} arguments at {@link #invokeLegacyMethod}
* or {@link #initializeLegacyClass}, to their mode'ed versions, based on the INPUT, OUTPUT and
* INPUT-OUTPUT modes.
*
* @param modes
* The caller's modes.
* @param args
* The caller's arguments.
*/
private static void prepareArgs(String modes, Object[] args)
{
if (args == null)
{
return;
}
for (int i = 0; i < args.length; i++)
{
Object arg = args[i];
Class<?> argType = arg.getClass();
if (argType == DataSetParameter.class)
{
DataSetParameter dsarg = (DataSetParameter) arg;
char mode = (modes == null ? 'I' : modes.charAt(i));
boolean isDsHandle = dsarg.getOriginalDataSet() == null;
switch (mode)
{
case 'I':
case 'i':
arg = isDsHandle ? new InputDataSetHandle(dsarg.getDatasetHandle(),
dsarg.getParameterOptions())
: new InputDataSetParameter(dsarg.getDataset(),
dsarg.getParameterOptions());
break;
case 'O':
case 'o':
arg = isDsHandle ? new OutputDataSetHandle(dsarg.getDatasetHandle(),
dsarg.getParameterOptions())
: new OutputDataSetParameter(dsarg.getDataset(),
dsarg.getParameterOptions());
break;
case 'U':
case 'u':
arg = isDsHandle ? new InputOutputDataSetHandle(dsarg.getDatasetHandle(),
dsarg.getParameterOptions())
: new InputOutputDataSetParameter(dsarg.getDataset(),
dsarg.getParameterOptions());
break;
}
}
else if (argType == TableParameter.class)
{
char mode = (modes == null ? 'I' : modes.charAt(i));
TableParameter tbarg = (TableParameter) arg;
boolean isTbHandle = tbarg.getTableHandle().isUnknown();
switch (mode)
{
case 'I':
case 'i':
arg = isTbHandle ? new InputTableHandle(tbarg.getTableHandle(),
tbarg.getParameterOptions())
: new InputTableParameter(tbarg.getTable(), tbarg.getParameterOptions());
break;
case 'O':
case 'o':
arg = isTbHandle ? new OutputTableHandle(tbarg.getTableHandle(),
tbarg.getParameterOptions())
: new OutputTableParameter(tbarg.getTable(), tbarg.getParameterOptions());
break;
case 'U':
case 'u':
arg = isTbHandle ? new InputOutputTableHandle(tbarg.getTableHandle(),
tbarg.getParameterOptions())
: new InputOutputTableParameter(tbarg.getTable(),
tbarg.getParameterOptions());
break;
}
}
else if (arg instanceof BaseDataType && BaseDataType.isProxy((BaseDataType) arg))
{
arg = ((BaseDataType) arg).val();
}
args[i] = arg;
}
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
{
/** Helper to use the ProcedureManager without any context local lookups. */
private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
/** Helper to use the TransactionManager without any context local lookups. */
private final TransactionManager.TransactionHelper tm = TransactionManager.getTransactionHelper();
/** Helper to use the ErrorManager without any context local lookups. */
private final ErrorManager.ErrorHelper em = ErrorManager.getErrorHelper();
/** The BlockManager helper - must be initialized in 'obtain' to avoid recursive initialization. */
private BlockManager.BlockManagerHelper bm = null;
/** Stores the most recent RETURN-VALUE for this user's session. */
private character returnValue = new character();
/**
* <code>true</code> if <code>RETURN-VALUE</code> was set during the call of the current root external
* program. Used to determine if we need to update <code>RETURN-VALUE</code> after an appserver call.
*/
private boolean returnValueSetInRootScope = false;
/**
* List of {@link Resolver} instances used for plain RUN statements.
*/
private final List<Resolver> resolvers;
/**
* List of {@link Resolver} instances used for RUN IN handle,
* DYNAMIC-FUNCTION(... IN handle) or FUNCTION ... IN handle cases.
*/
private final ArrayList<Resolver> inHandleResolvers;
/**
* {@link InternalResolver} instance which searches the internal-entries
* for a match.
*/
private final Resolver internalResolver;
/**
* {@link InternalProcSuperResolver} instance which searches the
* procedure's super-procedures for a match.
*/
private final Resolver internalProcSuperResolver;
/**
* {@link SessionSuperResolver} instance which searches the sessions's
* super-procedures for a match.
*/
private final Resolver sessionSuperResolver;
/**
* {@link ExternalProgramResolver} instance which searches the external
* program list for a match.
*/
private final ExternalProgramResolver externalResolver;
/**
* Flag indicating if this call is an invocation from a remote, customer-specific
* application. When this flag is on, only external programs defined via the
* {@link RemoteEntryPointResource} ACLs can be invoked. This flag needs to be reset BEFORE
* the remote API is invoked.
*/
private boolean remoteInvoke = false;
/** Flag indicating if last attempted invoke failed because of invalid arguments. */
private boolean invalidArgs = false;
/**
* A cache of the targets, for each call-site emitted in the converted code, by their external program
* instance.
*/
private Map<Object, Map<InvokeConfig, LastInvokeDetails>> callSiteCache = new IdentityHashMap<>();
/** A cache of the targets for external program calls, for each call-site.*/
private Map<InvokeConfig, LastInvokeDetails> extProgcallSiteCache = new IdentityHashMap<>();
/** The current invocation, used to cache the target when is not null. */
private InvokeConfig currentInvoke = null;
/** The current caller where to cache the invocation. */
private Object currentCaller = null;
/** Cached resolved procedure internal entries */
private HashMap<InternalResolver.InternalEntryCacheKey, InternalEntry> cachedIEntries = new HashMap<>();
private WorkArea()
{
// if PROPATH changes, then only external programs are affected - clear only that cache.
EnvironmentOps.addSourcePathListener((path) -> extProgcallSiteCache.clear());
internalResolver = new InternalResolver(this);
internalProcSuperResolver = new InternalProcSuperResolver(this);
sessionSuperResolver = new SessionSuperResolver(this);
externalResolver = new ExternalProgramResolver(this);
resolvers = new ArrayList<>();
resolvers.add(internalResolver);
resolvers.add(internalProcSuperResolver);
resolvers.add(sessionSuperResolver);
resolvers.add(externalResolver);
inHandleResolvers = new ArrayList<>();
inHandleResolvers.add(internalResolver);
inHandleResolvers.add(internalProcSuperResolver);
inHandleResolvers.add(sessionSuperResolver);
}
}
/**
* Simple container that stores and returns a context-local instance of
* the global work area.
*/
private static class ContextContainer
extends ContextLocal<WorkArea>
{
/**
* Obtains the context-local instance of the contained global work
* area.
*
* @return The work area associated with this context.
*/
public WorkArea obtain()
{
WorkArea wa = this.get();
if (wa.bm == null)
{
wa.bm = BlockManager.getHelper();
}
return wa;
}
/**
* Initializes the work area, the first time it is requested within a
* new context.
*
* @return The newly instantiated work area.
*/
protected synchronized WorkArea initialValue()
{
return new WorkArea();
}
}
/**
* Helper to expose APIs which have direct access to the context-local state.
*/
static class ControlFlowOpsHelper
{
/** The {@link WorkArea} instance. */
private final WorkArea wa;
/**
* Create a new instance and associate the given WorkArea instance.
*
* @param wa
* The {@link WorkArea} instance.
*/
public ControlFlowOpsHelper(WorkArea wa)
{
this.wa = wa;
}
/**
* Sets the most recent RETURN-VALUE for the current user's session.
*
* @param returnValue
* The RETURN-VALUE to set or <code>null</code> to assign the
* empty string.
*/
public void setReturnValue(character returnValue)
{
if (returnValue == null)
{
wa.returnValue.assign(character.EMPTY_STRING);
}
else
{
wa.returnValue.assign(returnValue);
}
wa.returnValueSetInRootScope = wa.tm.isRootExternal();
}
/**
* Sets the most recent RETURN-VALUE for the current user's session.
*
* @param returnValue
* The RETURN-VALUE to set or <code>null</code> to assign the
* empty string.
*/
public void setReturnValue(String returnValue)
{
wa.returnValue.assign(returnValue == null ? "" : returnValue);
wa.returnValueSetInRootScope = wa.tm.isRootExternal();
}
}
/**
* Invoke the configured internal entry.
*/
private static class InternalEntryCaller
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(InternalEntryCaller.class);
/** Cache of methods by type, name, class, and signature */
private static final Cache<CacheKey, CacheValue> cache = new LRUCache<>(8192);
/** The external program instance where the internal entry's body is defined. */
protected Object callerInstance;
/** The java name of this internal entry. */
protected String methodName;
/** The legacy 4GL name of this internal entry. */
protected String iename;
/** The {@link WorkArea} instance. */
protected WorkArea wa;
/** The {@link Method} instance for this internal entry. */
private Method mthd = null;
/** The ReflectASM access point to this method. */
private MethodAccess access = null;
/** The method index in the MethodAccess instance. */
private int index = -1;
/** The handle associated with the {@link #callerInstance}. */
private handle phandle;
/** Worker which performs the {@link ProcedureManager.ProcedureHelper#pushCalleeInfo} call. */
private Runnable pushWorker;
/** Already resolved internal entry (used when invoking OO methods). */
private InternalEntry ie = null;
/**
* Basic c'tor.
*
* @param wa
* The context-local state in {@link WorkArea}.
*/
public InternalEntryCaller(WorkArea wa)
{
this.wa = wa;
}
/**
* Configure this instance to invoke the given internal entry.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param phandle
* The procedure handle where the internal entry's body is
* defined.
* @param methodName
* The resolved java name for this internal entry.
* @param iename
* The legacy 4GL name of this internal entry.
*/
public InternalEntryCaller(WorkArea wa, handle phandle, String methodName, String iename)
{
this(wa);
this.phandle = new handle(phandle);
this.callerInstance = phandle.get();
this.methodName = methodName;
this.iename = iename;
}
/**
* Set the {@link #pushWorker}.
*
* @param push
* The code which performs the {@link ProcedureManager.ProcedureHelper#pushCalleeInfo} call.
*/
void setPushWorker(Runnable push)
{
this.pushWorker = push;
}
/**
* Invoke the internal entry, using the {@link #mthd},
* the {@link #callerInstance} and the passed arguments.
* <p>
* It performs an additional check, in case of remote invocation calls originating from
* external, customer-specific application (when {@link WorkArea#remoteInvoke} flag is set):
* access to the external program must be allowed via a {@link RemoteEntryPointResource} ACL.
*
* @param modes
* An encoded string with the mode of each parameter specified as a
* character in the string.
* @param param
* The arguments for this Java method call.
*
* @return The returned value
*
* @throws Exception
* If any exceptions are encountered during method invocation.
*/
final Object invoke(String modes, Object... param)
throws Exception
{
WorkArea wa = work.obtain();
if (wa.remoteInvoke)
{
wa.remoteInvoke = false;
String pname = getExternalProgramName();
SecurityManager sm = SecurityManager.getInstance();
RemoteEntryPointResource repr =
(RemoteEntryPointResource) sm.getPluginInstance("remoteentrypoint");
if (repr == null || !repr.isAllowed(pname))
{
String msg = (repr == null) ?
String.format("Remote program '%s' cannot be accessed because the " +
"RemoteEntryPointResource is NOT enabled.", pname) :
String.format("Access to remote program '%s' not allowed for user %s.",
pname,
sm.getUserId());
LOG.severe(msg);
throw new SilentUnwindException(new IllegalStateException(msg));
}
}
return invokeImpl(modes, param);
}
/**
* Invoke the internal entry, using the {@link #mthd},
* the {@link #callerInstance} and the passed arguments.
*
* @param modes
* An encoded string with the mode of each parameter specified as a
* character in the string.
* @param param
* The arguments for this Java method call.
*
* @return The returned value
*
* @throws Exception
* If any exceptions are encountered during method invocation.
*/
Object invokeImpl(String modes, Object... param)
throws Exception
{
InvokeConfig currentInvoke = wa.currentInvoke;
Object currentCaller = wa.currentCaller;
if (currentInvoke != null)
{
// the InternalEntryCaller.mthd will be affected by other calls, cache it now.
cacheInvocation(wa, this, currentInvoke, currentCaller, param);
}
// the reference is no longer useful, reset it
wa.currentInvoke = null;
wa.currentCaller = null;
boolean isStatic = Modifier.isStatic(mthd.getModifiers());
Object instance = isStatic ? null : callerInstance;
try
{
pushWorker.run();
wa.bm.markPendingDynCall();
if (access != null)
{
return access.invoke(instance, index, param);
}
else
{
mthd.setAccessible(true);
return mthd.invoke(instance, param);
}
}
finally
{
wa.pm.popCalleeInfo();
}
}
/**
* Get the signature, as defined by the parameters.
*
* @param param
* The arguments for this Java method call.
*
* @return An array with the type of each parameter.
*/
static Class<?>[] getSignature(Object... param)
{
Class<?>[] signature = new Class[param.length];
for (int i = 0; i < signature.length; i++)
{
Class<?> type;
Object p = param[i];
if (p instanceof BufferReference)
{
// the only provided support is for buffer objects.
Class<?> dmoBufIfc = RecordBuffer.getDMOBufInterfaceForObject(p);
if (dmoBufIfc == null)
{
throw new RuntimeException("Dynamic proxy not supported for parameter " + p);
}
type = dmoBufIfc;
}
else
{
type = p == null ? Object.class : p.getClass();
}
if (type.isAnonymousClass())
{
type = type.getSuperclass();
}
signature[i] = type;
}
return signature;
}
/**
* Validate the given parameters against the method's real signature using the corresponding
* parameter passing mode:
* <ul>
* <li><strong>INPUT</strong>If the parameter's type is compatible with the real type
* (from the {@link #mthd method's} arguments), then cast it to it.
* <li><strong>OUTPUT</strong>If the real type is compatible with the parameter's type
* (from the {@link #mthd method's} arguments), the use a default value for the
* expected type. The compatibility assures the conversion will be possible at
* return time.
* <li><strong>INPUT-OUTPUT</strong>Both conversion must be available for this parameter
* to be validated. A cast at this moment will assure INPUT compatibility and a
* second cast when returning will also be needed.
* </ul>
* INPUT
*
* If all are valid, then {@link ArgValidationErrors#NO_ERROR} is returned. Else:
* <ul>
* <li>if the argument count does not match, then
* {@link ArgValidationErrors#INCORRECT_COUNT} is returned.</li>
* <li>if the types of the arguments do not match, then
* {@link ArgValidationErrors#INCORRECT_TYPES} is returned.</li>
* </ul>
*
* @param function
* <code>true</code> if this is a function call.
* @param modes
* The types of parameters: INPUT(I) / OUTPUT (O) / INPUT-OUTPUT (U).
* @param extentInfo
* An output parameter array that provides the caller with additional information
* about the extent sizes when they don't match. The first item is the size of the
* argument (provided extent), the second is the parameter (expected extent).
* @param param
* The arguments for this Java method call.
*
* @return One of the {@link ArgValidationErrors} constants.
*/
ArgValidationErrors valid(boolean function, String modes, int[] extentInfo, Object... param)
{
return valid(this, function, modes, extentInfo, param);
}
/**
* Validate the given parameters against the method's real signature using the corresponding
* parameter passing mode:
* <ul>
* <li><strong>INPUT</strong>If the parameter's type is compatible with the real type
* (from the {@link #mthd method's} arguments), then cast it to it.
* <li><strong>OUTPUT</strong>If the real type is compatible with the parameter's type
* (from the {@link #mthd method's} arguments), the use a default value for the
* expected type. The compatibility assures the conversion will be possible at
* return time.
* <li><strong>INPUT-OUTPUT</strong>Both conversion must be available for this parameter
* to be validated. A cast at this moment will assure INPUT compatibility and a
* second cast when returning will also be needed.
* </ul>
* INPUT
*
* If all are valid, then {@link ArgValidationErrors#NO_ERROR} is returned. Else:
* <ul>
* <li>if the argument count does not match, then
* {@link ArgValidationErrors#INCORRECT_COUNT} is returned.</li>
* <li>if the types of the arguments do not match, then
* {@link ArgValidationErrors#INCORRECT_TYPES} is returned.</li>
* </ul>
*
* @param caller
* The {@link InternalEntryCaller} instance.
* @param function
* <code>true</code> if this is a function call.
* @param modes
* The types of parameters: INPUT(I) / OUTPUT (O) / INPUT-OUTPUT (U).
* @param extentInfo
* An output parameter array that provides the caller with additional information
* about the extent sizes when they don't match. The first item is the size of the
* argument (provided extent), the second is the parameter (expected extent).
* @param param
* The arguments for this Java method call.
*
* @return One of the {@link ArgValidationErrors} constants.
*/
private static ArgValidationErrors valid(InternalEntryCaller caller,
boolean function,
String modes,
int[] extentInfo,
Object... param)
{
// convert the parameters to BDT, if possible
for (int i = 0; i < param.length; i++)
{
Object p = param[i];
Object n = p;
if (p instanceof Long)
{
n = new int64((Long) p);
}
else if (p instanceof Double)
{
n = new decimal((Double) p);
}
else if (p instanceof Float)
{
n = new decimal((Float) p);
}
else if (p instanceof Number)
{
n = new integer((Number) p);
}
else if (p instanceof Boolean)
{
n = new logical((Boolean) p);
}
else if (p instanceof String)
{
n = new character((String) p);
}
param[i] = n;
}
Class<?>[] signature = getSignature(param); // required signature
Integer[] parametersExtent = new Integer[param.length];
for (int i = 0; i < param.length; i++)
{
if (param[i] != null && param[i].getClass().isArray())
{
parametersExtent[i] = Array.getLength(param[i]);
}
}
Class<?> cls = caller.callerInstance.getClass();
ArgValidationErrors rv;
// a method may be set when invoking a legacy OO method - here we just need to prepare
// and do a final validation of the arguments
Method method = caller.mthd;
// check the cache first; we not only cache successful lookups, but also remember failed
// lookups, so we don't repeat them (it is quite expensive to fail to find a method, as
// it involves two exceptions being thrown, a search through all methods of the class,
// and a good bit of fuzzy matching logic)
CacheKey key = new CacheKey(function, cls, caller.methodName, signature, parametersExtent);
CacheValue value = getCache(key, method); // TODO: rename [value] with a more appropriate name
if (method == null)
{
if (value != null)
{
// only return immediately if we had an exact signature match, or if we failed to
// find a method at all previously for this set of criteria; for a fuzzy match, we
// still have parameter type massaging work to do below
if (value.exactMatch)
{
if (value.method != null)
{
caller.mthd = value.method;
caller.access = value.access;
caller.index = value.index;
}
return value.returnValue;
}
}
else
{
try
{
method = cls.getMethod(caller.methodName, signature);
LegacySignature legacySignature = method.getAnnotation(LegacySignature.class);
LegacyParameter[] legacyParameters = legacySignature.parameters();
if (legacyParameters.length != param.length)
{
// cache failed result as well, so we don't need to repeat validation
rv = ArgValidationErrors.INCORRECT_COUNT;
putCache(key, null, rv, true);
return rv;
}
// check for the right extent parameters
for (int i = 0; i < param.length; i++)
{
if (parametersExtent[i] != null)
{
int expectedExt = legacyParameters[i].extent();
if (!parametersExtent[i].equals(expectedExt) &&
expectedExt != SourceNameMapper.DYNAMIC_EXTENT)
{
// cache failed result as well, so we don't need to repeat validation
rv = ArgValidationErrors.BAD_ARRAY_DIMENSION;
putCache(key, null, rv, true);
return rv;
}
}
}
rv = ArgValidationErrors.NO_ERROR;
value = putCache(key, method, rv, true);
caller.mthd = value.method;
caller.access = value.access;
caller.index = value.index;
return rv;
}
catch (Exception e)
{
method = null;
}
// fallback - check if the method is declared, but private
try
{
method = cls.getDeclaredMethod(caller.methodName, signature);
rv = ArgValidationErrors.NO_ERROR;
value = putCache(key, method, rv, true);
caller.mthd = value.method;
caller.access = value.access;
caller.index = value.index;
return rv;
}
catch (Exception e)
{
method = null;
}
}
}
// TODO: add support for the longchar-to-char narrowing and char-to-longchar widening
final Class<?> AEP = AbstractExtentParameter.class;
final String extProgName = ProcedureManager.getAbsoluteName(caller.callerInstance);
// go through all the methods (or the cached one only), and do some fuzzy parameter type
// matching
Method[] methods = method != null
? new Method[] { method }
: (value != null)
? new Method[] { value.method }
: cls.getDeclaredMethods();
boolean correctNo = false;
boolean hasParams = false;
boolean cacheFailedResult = true;
for (Method m : methods)
{
if (!m.getName().equals(caller.methodName))
{
// if the name of the method does not match skip to next method
continue;
}
Class<?>[] mtypes = m.getParameterTypes();
hasParams = (mtypes.length > 0);
if (mtypes.length != param.length)
{
// if parameter count does not match skip this method
continue;
}
if (modes == null)
{
// if the call comes from ControlFlowOps.runSuper(), the modes are unknown
modes = SourceNameMapper.getParameterModes(extProgName, caller.ie, caller.iename, function);
// for builtin classes, there is no mapping (yet) - assume always INPUT
// TODO: annotations with parameter modes?
if (modes == null && caller.callerInstance instanceof BaseObject)
{
modes = "";
for (int i = 0; i < param.length; i++)
{
modes = modes + "I";
}
}
}
// save the "closest" method match in case of failure
if (caller.mthd == null)
{
caller.mthd = m;
}
correctNo = true;
LegacySignature lsig = m.getAnnotation(LegacySignature.class);
BiFunction<Class<?>, Class<?>, Constructor<?>> defaultCtor = (p1, p2) ->
{
Constructor<?> ctor = Utils.findConstructor(p1, p2);
if (ctor == null)
{
throw new RuntimeException("Can't find " + p1 + " ctor for " + p2);
}
return ctor;
};
BiFunction<Class<?>, Class<?>, Constructor<?>> objectCtor = (p1, p2) ->
{
try
{
return p1.getConstructor(Class.class, p2);
}
catch (NoSuchMethodException |
SecurityException e)
{
throw new RuntimeException(e);
}
};
Class<?>[] qualifiedType = new Class<?>[1];
BiFunction<Constructor<?>, Object, BaseDataType> defaultInstance = (p1, p2) ->
{
try
{
return (BaseDataType) p1.newInstance(p2);
}
catch (ReflectiveOperationException |
IllegalArgumentException e)
{
throw new RuntimeException(e);
}
};
BiFunction<Constructor<?>, Object, BaseDataType> objectInstance = (p1, p2) ->
{
try
{
return (BaseDataType) p1.newInstance(qualifiedType[0], p2);
}
catch (IllegalArgumentException |
ReflectiveOperationException e)
{
throw new RuntimeException(e);
}
};
// try to cast each parameter to the expected type. If it works, then use this method
boolean ok = true;
Object[] newPars = new Object[param.length];
for (int i = 0; i < param.length; i++)
{
newPars[i] = param[i];
Class<?> pcls = param[i].getClass();
if (pcls.isAnonymousClass())
{
pcls = pcls.getSuperclass();
}
Class<?> argType = signature[i];
Class<?> expectedType = mtypes[i];
qualifiedType[0] = null;
BiFunction<Class<?>, Class<?>, Constructor<?>> parCtor = defaultCtor;
BiFunction<Constructor<?>, Object, BaseDataType> instance = defaultInstance;
if (lsig != null)
{
LegacyParameter lpar = lsig.parameters()[i];
if (lpar.type().equals("JOBJECT") &&
lpar.qualified() != null &&
!lpar.qualified().isEmpty())
{
try
{
qualifiedType[0] = Class.forName(lpar.qualified());
parCtor = objectCtor;
instance = objectInstance;
}
catch (ClassNotFoundException e)
{
// something went wrong...
LOG.log(Level.SEVERE, "Can't resolve qualified type " + lpar.qualified(), e);
ok = false;
break;
}
}
// check for wrong extent lengths
if (parametersExtent[i] != null)
{
int expectedExt = lpar.extent();
if (expectedExt != SourceNameMapper.DYNAMIC_EXTENT &&
!parametersExtent[i].equals(expectedExt))
{
// cache failed result as well, so we don't need to repeat validation
rv = ArgValidationErrors.BAD_ARRAY_DIMENSION;
putCache(key, null, rv, true);
return rv;
}
}
}
// check if passing a non-array to an array or an array to non-array.
// do not check arrays containing non-BDT components.
if (value == null &&
((argType.isArray() != expectedType.isArray() &&
(!argType.isArray() ||
BaseDataType.class.isAssignableFrom(argType.getComponentType()))) ||
AEP.isAssignableFrom(argType) != AEP.isAssignableFrom(expectedType)))
{
boolean parExtent = argType.isArray() || AEP.isAssignableFrom(argType);
int expectedExtent = SourceNameMapper.getExtentForParam(extProgName,
caller.ie,
caller.iename,
function,
i);
int paramExtent = 0;
if (parExtent)
{
BaseDataType[] pvar = null;
if (AEP.isAssignableFrom(argType))
{
pvar = ((AbstractExtentParameter) newPars[i]).getVariableSafe();
}
else
{
pvar = (BaseDataType[]) newPars[i];
}
paramExtent = pvar.length;
}
if (expectedExtent == 0)
{
// expected param is dynamic-extent
rv = parExtent
? ArgValidationErrors.DYN_ARRAY_FOR_NON_ARRAY
: ArgValidationErrors.NON_ARRAY_FOR_DYN_ARRAY;
}
else
{
// expected param is fixed-extent
rv = parExtent
? ArgValidationErrors.ARRAY_FOR_NON_ARRAY
: ArgValidationErrors.NON_ARRAY_FOR_ARRAY;
}
extentInfo[1] = parExtent ? 0 : expectedExtent;
extentInfo[0] = parExtent ? paramExtent : 0;
// cache failed result as well, so we don't need to repeat validation
putCache(key, null, rv, true);
return rv;
}
// provide compatibility between resources and handles
if (WrappedResource.class.isAssignableFrom(argType) &&
handle.class.equals(expectedType))
{
newPars[i] = new handle((WrappedResource) param[i]);
// TODO: check the OUTPUT mode case
continue;
}
// provide compatibility between comhandles and handles
if (comhandle.class.isAssignableFrom(argType) &&
handle.class.equals(expectedType))
{
newPars[i] = new handle((comhandle) param[i]);
// TODO: check the OUTPUT mode case
continue;
}
if (!expectedType.isAssignableFrom(argType))
{
boolean wasPending = caller.wa.em.isPending();
caller.wa.em.setPending(false);
try
{
// if the param's type is unknown class, construct the unknown for the
// expectedType
if (unknown.class.equals(argType))
{
// for some data types (date, datetime, datetime-tz, raw) the
// unknown value is not created using default constructor
newPars[i] = BaseDataType.generateUnknown(expectedType);
// TODO: check the OUTPUT mode case
}
else
{
Class<?> toCompType;
Class<?> fromCompType;
boolean isArray = expectedType.isArray();
if (isArray)
{
// take into consideration the elements' type
toCompType = expectedType.getComponentType();
fromCompType = pcls.getComponentType();
}
else
{
toCompType = expectedType;
fromCompType = pcls;
}
Constructor<?> fwCtor;
Constructor<?> bwCtor = null;
// checking the parameter passing type: IN, OUT, IN-OUT
switch (modes.charAt(i))
{
case 'I':
boolean i64Quirk = toCompType == integer.class && fromCompType == int64.class;
if (i64Quirk)
{
Function<int64, integer> farg =
(v) -> InputParameter.function(integer.class, v);
Function<int64, integer> parg =
(v) -> InputParameter.procedure(integer.class, v);
Function<int64, integer> zarg = function ? farg : parg;
if (isArray)
{
// copy the newPars[i] array through fwCtor morphism
Object[] arrayArg = (Object[]) newPars[i];
Object[] arrayParam = (Object[])
Array.newInstance(toCompType, arrayArg.length);
for (int k = 0; k < arrayArg.length; k++)
{
arrayParam[k] = zarg.apply((int64) arrayArg[i]);
}
newPars[i] = arrayParam;
}
else
{
newPars[i] = zarg.apply((int64) param[i]);
}
}
else
{
try
{
fwCtor = parCtor.apply(toCompType, fromCompType);
}
catch (Throwable e)
{
// on failure try the 'generic' conversion constructor
fwCtor = parCtor.apply(toCompType, BaseDataType.class);
}
if (isArray)
{
// copy the newPars[i] array through fwCtor morphism
Object[] arrayArg = (Object[]) newPars[i];
Object[] arrayParam = (Object[])
Array.newInstance(toCompType, arrayArg.length);
for (int k = 0; k < arrayArg.length; k++)
{
arrayParam[k] = instance.apply(fwCtor, arrayArg[i]);
}
newPars[i] = arrayParam;
}
else
{
newPars[i] = instance.apply(fwCtor, param[i]);
}
}
break;
case 'O':
// check existence of the return cast:
if (toCompType == jobject.class || fromCompType == jobject.class)
{
bwCtor = fromCompType.getConstructor(BaseDataType.class);
}
else
{
bwCtor = fromCompType.getConstructor(toCompType);
}
if (qualifiedType[0] != null)
{
newPars[i] = new jobject(qualifiedType[0]);
}
else
{
// the output parameters aren't / don't need to be initiated
newPars[i] = BaseDataType.generateDefault(toCompType);
}
break;
case 'U':
// check existence of the return cast:
if (fromCompType == jobject.class)
{
bwCtor = fromCompType.getConstructor(BaseDataType.class);
}
else
{
bwCtor = fromCompType.getConstructor(toCompType);
}
if (toCompType == jobject.class)
{
fwCtor = toCompType.getConstructor(Class.class, BaseDataType.class);
}
else
{
fwCtor = toCompType.getConstructor(fromCompType);
}
newPars[i] = instance.apply(fwCtor, param[i]);
break;
case 'B':
// check the DMO compatibility between argType and expectedType
// 1. DMOs will match even when the table options are different (e.g. no-undo status)
// 2. The table name is important when checking the DMO compatibility
Class<? extends DataModelObject> argDmo = DmoMetadataManager.getDmoIface(argType);
Class<? extends DataModelObject> expectedDmo = DmoMetadataManager.getDmoIface(expectedType);
if (DmoMetadataManager.isSchemaMatch(argDmo, expectedDmo, true))
{
// create a proxy from the expectedType to the argument's instance
newPars[i] = RecordBuffer.createProxy(expectedType, param[i]);
}
break;
}
if (bwCtor != null)
{
// in the event the OUTPUT value must be converted back:
FieldAssigner.update(((BaseDataType) param[i]),
((BaseDataType) newPars[i]),
bwCtor);
}
}
if (caller.wa.em.isPending())
{
// if any errors occurred during parameter matching
// (eg.: INTEGER overflow) procedure cannot be called
ok = false;
break;
}
}
catch (Throwable t)
{
// was not able to convert this param
ok = false;
// check if a condition was raised. if so, do not
// search for any other c'tors
boolean raisedCondition = false;
while (t != null)
{
if (t instanceof ConditionException)
{
raisedCondition = true;
cacheFailedResult = false;
}
t = t.getCause();
}
if (raisedCondition)
{
break;
}
}
finally
{
caller.wa.em.setPending(caller.wa.em.isPending() || wasPending);
}
// fallback, maybe there is a c'tor with a compatible type
if (!ok)
{
Constructor<?>[] ctors;
Constructor<?> bwCtor = null;
// checking the parameter passing type: IN, OUT, IN-OUT
switch (modes.charAt(i))
{
case 'I':
ctors = expectedType.getConstructors();
for (Constructor<?> ctor : ctors)
{
Class<?>[] ctypes = ctor.getParameterTypes();
if (ctypes.length == 1 && ctypes[0].isAssignableFrom(pcls))
{
try
{
newPars[i] = ctor.newInstance(param[i]);
ok = true;// found my c'tor
break;
}
catch (Exception e)
{
// ignore, ok stays false
}
}
}
break;
case 'O':
// check existence of the return cast:
ctors = pcls.getConstructors();
for (Constructor<?> ctor : ctors)
{
Class<?>[] ctypes = ctor.getParameterTypes();
if (ctypes.length == 1 &&
ctypes[0].isAssignableFrom(expectedType))
{
try
{
bwCtor = ctor;
newPars[i] = BaseDataType.generateDefault(expectedType);
ok = true; // found my return cast c'tor
break; // for each c'tor
}
catch (Exception e)
{
// ignore, ok stays false
}
}
}
break;
case 'U':
// check existence of the return cast:
ctors = pcls.getConstructors();
for (Constructor<?> ctor : ctors)
{
Class<?>[] ctypes = ctor.getParameterTypes();
if (ctypes.length == 1 &&
ctypes[0].isAssignableFrom(expectedType))
{
try
{
bwCtor = ctor;
ok = true; // found my return cast c'tor
break; // for each c'tor
}
catch (Exception e)
{
// ignore, ok stays false
}
}
}
// but use as standard INPUT for now, if it exist
if (ok)
{
ok = false;
ctors = expectedType.getConstructors();
for (Constructor<?> ctor : ctors)
{
Class<?>[] ctypes = ctor.getParameterTypes();
if (ctypes.length == 1 && ctypes[0].isAssignableFrom(pcls))
{
try
{
newPars[i] = ctor.newInstance(param[i]);
ok = true;// found my c'tor
break;
}
catch (Exception e)
{
// ignore, ok stays false
}
}
}
}
if (!ok)
{
// if forward conversion fails, bwCtor is useless
bwCtor = null;
}
break;
} // ~switch (mode)
if (bwCtor != null)
{
// in the event the OUTPUT value must be converted back:
FieldAssigner.update(((BaseDataType) param[i]),
((BaseDataType) newPars[i]),
bwCtor);
}
}
// if still wasn't found, then exit.
if (!ok)
{
break;
}
}
else if (AEP.isAssignableFrom(expectedType) &&
AEP.isAssignableFrom(argType))
{
AbstractExtentParameter aep = (AbstractExtentParameter) newPars[i];
int argExt = aep.getVariableSafe().length;
int paramExt = SourceNameMapper.getExtentForParam(extProgName,
caller.ie,
caller.iename,
function,
i);
if (paramExt > 0 && paramExt != argExt)
{
// if paramExt is 0 it is dynamic extent so any extent is acceptable
extentInfo[0] = argExt;
extentInfo[1] = paramExt;
return ArgValidationErrors.BAD_ARRAY_DIMENSION; // cache it?
}
Class<?> argTypeE =
((AbstractExtentParameter) param[i]).getVariableSafe().getClass().getComponentType();
Class<?> paramTypeE =
SourceNameMapper.getTypeForParam(extProgName, caller.ie, caller.iename, function, i);
if (argTypeE != paramTypeE)
{
Constructor<?> fwCtor = null;
Constructor<?> bwCtor = null;
try
{
// checking the parameter passing type: IN, OUT, IN-OUT
switch (modes.charAt(i))
{
case 'I':
// this should not be normally reachable,
// here we handle only wrapped EXTENT fields/variable
throw new RuntimeException("Invalid execution path");
case 'U':
try
{
fwCtor = paramTypeE.getConstructor(argTypeE);
}
catch (NoSuchMethodException e)
{
// on failure try the 'generic' conversion constructor
fwCtor = paramTypeE.getConstructor(BaseDataType.class);
}
// no break, fall through!
case 'O':
// check existence of the return cast:
try
{
bwCtor = argTypeE.getConstructor(paramTypeE);
}
catch (NoSuchMethodException e)
{
// on failure try the 'generic' conversion constructor
bwCtor = argTypeE.getConstructor(BaseDataType.class);
}
aep.setReturnConversion(argTypeE, paramTypeE, fwCtor, bwCtor);
break;
}
}
catch (Throwable t)
{
// was not able to convert this param
ok = false;
// check if a condition was raised. if so, do not search for any
// other c'tors
boolean raisedCondition = false;
while (t != null)
{
if (t instanceof ConditionException)
{
raisedCondition = true;
}
t = t.getCause();
}
if (raisedCondition)
{
break;
}
}
}
// otherwise should be no problem, the same class of array elements
}
}
if (ok)
{
// was able to cast the parameters to expected types
System.arraycopy(newPars, 0, param, 0, param.length);
rv = ArgValidationErrors.NO_ERROR;
if (value == null)
{
value = putCache(key, m, rv, false);
}
caller.mthd = value.method;
caller.access = value.access;
caller.index = value.index;
return rv;
}
} // ~ for each method
// we failed fuzzy parameter matching; determine what type of error to report
rv = !correctNo
? (!hasParams ? ArgValidationErrors.UNEXPECTED_PARAM
: ArgValidationErrors.INCORRECT_COUNT)
: ArgValidationErrors.INCORRECT_TYPES;
//try to specialize the ArgValidationErrors.INCORRECT_TYPES by checking the extents
if (rv == ArgValidationErrors.INCORRECT_TYPES)
{
method = caller.mthd;
LegacySignature legacySignature = method.getAnnotation(LegacySignature.class);
LegacyParameter[] legacyParameters = legacySignature.parameters();
// check for the right extent parameters
for (int i = 0; i < param.length; i++)
{
if (parametersExtent[i] != null)
{
int expectedExt = legacyParameters[i].extent();
if (!parametersExtent[i].equals(expectedExt))
{
// cache failed result as well, so we don't need to repeat validation
rv = ArgValidationErrors.BAD_ARRAY_DIMENSION;
putCache(key, null, rv, true);
break;
}
}
}
}
if (cacheFailedResult)
{
// cache failed result as well, so we don't need to repeat validation
putCache(key, null, rv, true);
}
return rv;
}
/**
* Get the java method name for this internal entry.
*
* @return See above.
*/
String getMethodName()
{
return methodName;
}
/**
* Get the class name of the external program to which this internal
* entry belongs.
*
* @return See above.
*/
String getClassName()
{
return callerInstance.getClass().getName();
}
/**
* Get the external program instance where this internal entry is
* defined.
*
* @return See above.
*/
Object getCallerInstance()
{
return callerInstance;
}
/**
* Get the legacy name of this internal entry.
*
* @return See above.
*/
String getInternalEntryName()
{
return iename;
}
/**
* Get the legacy name of the external program where the execution will take place.
*
* @return See above.
*/
String getExternalProgramName()
{
return ProcedureManager.getAbsoluteName(callerInstance);
}
/**
* Get the string representation of the parameter modes defined for this
* internal entry.
*
* @param function
* <code>true</code> if this is a function call.
*
* @return See above.
*/
String getParameterModes(boolean function)
{
if (ie != null)
{
return ie.getParameterModes();
}
String pname = ProcedureManager.getAbsoluteName(callerInstance);
String iename = getInternalEntryName();
return SourceNameMapper.getParameterModes(pname, ie, iename, function);
}
/**
* Get the parameters defined for this internal entry.
*
* @param function
* <code>true</code> if this is a function call.
*
* @return See above.
*/
List<Parameter> getParameters(boolean function)
{
if (ie != null)
{
return ie.getParameterList();
}
String pname = ProcedureManager.getAbsoluteName(callerInstance);
String iename = getInternalEntryName();
return SourceNameMapper.getParameters(pname, ie, iename, function);
}
/**
* Get the {@link #phandle} field.
*
* @return See above.
*/
handle getPhandle()
{
return phandle;
}
/**
* Get a cached value, if any, for the given key.
*
* @param key
* Cache key.
* @param mthd
* Method instance to validate against the cached value. This is needed in OO dynamic invoke
* cases where the method resolution is not done via the runtime type, but via a type determined
* at conversion type. May be <code>null</code>.
*
* @return Cached value, or <code>null</code>.
*/
private static CacheValue getCache(CacheKey key, Method mthd)
{
CacheValue value;
synchronized (cache)
{
value = cache.get(key);
// if the target method is known, the resolved method must be the same
if (value != null &&
mthd != null &&
value.method != null &&
!value.method.equals(mthd))
{
// this can happen in dynamic poly cases, where the lookup type is enforced from conversion
cache.remove(key);
value = null;
}
}
return value;
}
/**
* Cache the specified information under the given cache key.
*
* @param key
* Cache key.
* @param method
* Method found matching a given name and signature, or {@code null} if no
* match was found.
* @param returnValue
* Enumerated return value for {@link #valid(boolean, String, int[], Object...)}
* method.
* @param exactMatch
* <code>true</code> to indicate the method signature stored in the key exactly
* matches that of the cached method; <code>false</code> to indicate one or more
* method arguments needs to be converted to a compatible type.
*
* @return the created cache.
*/
private static CacheValue putCache(CacheKey key,
Method method,
ArgValidationErrors returnValue,
boolean exactMatch)
{
CacheValue value = new CacheValue(method, returnValue, exactMatch);
synchronized (cache)
{
cache.put(key, value);
}
return value;
}
/**
* A cache key which encapsulates the name of a method, its signature and containing class,
* and whether that method represents a converted Progress function or a procedure.
*/
private static class CacheKey
{
/** <code>true</code> if method represents a Progress function */
final boolean function;
/** Method's enclosing class */
final Class<?> callerClass;
/** Name of the method */
final String methodName;
/** Method's signature types */
final Class<?>[] signature;
/** The coresponding parameter's extent */
final Integer[] paramExtent;
/**
* Constructor.
*
* @param function
* <code>true</code> if method represents a Progress function.
* @param callerClass
* Method's enclosing class.
* @param methodName
* Name of the method.
* @param signature
* Method's signature types.
* @param paramExtent
* The extent of the parameters of the method.
*/
CacheKey(boolean function,
Class<?> callerClass,
String methodName,
Class<?>[] signature,
Integer[] paramExtent)
{
this.function = function;
this.callerClass = callerClass;
this.methodName = methodName;
this.signature = signature;
this.paramExtent = paramExtent;
}
/**
* Generate this object's hash code, based on its content, in a manner consistent with
* {@link #equals(Object)}.
*
* @return Hash code.
*/
@Override
public int hashCode()
{
int result = 17;
result = 37 * result + (function ? 0 : 1);
result = 37 * result + callerClass.hashCode();
result = 37 * result + methodName.hashCode();
result = 37 * result + Arrays.hashCode(signature);
result = 37 * result + Arrays.hashCode(paramExtent);
return result;
}
/**
* Indicate whether this object is equivalent with the given object, based on both of
* their type and content, in a manner consistent with {@link #hashCode()}.
*
* @return <code>true</code> if this object is equivalent with the given object, else
* <code>false</code>.
*/
@Override
public boolean equals(Object o)
{
if (!(o instanceof CacheKey))
{
return false;
}
CacheKey that = (CacheKey) o;
if (this.function != that.function)
{
return false;
}
if (!this.callerClass.equals(that.callerClass))
{
return false;
}
if (!this.methodName.equals(that.methodName))
{
return false;
}
int len = signature.length;
if (len != that.signature.length)
{
return false;
}
for (int i = 0; i < len; i++)
{
if (!this.signature[i].equals(that.signature[i]))
{
return false;
}
// Objects.equals handles null references too
if (!Objects.equals(this.paramExtent[i], that.paramExtent[i]))
{
return false;
}
}
return true;
}
}
/**
* A data structure for the cache's values.
*/
private static class CacheValue
{
/** Method, if any, else <code>null</code> if none was found */
final Method method;
/** The ReflectASM access point to this method. */
final MethodAccess access;
/** The method index in the MethodAccess instance. */
final int index;
/**
* Enumerated return value for {@link
* InternalEntryCaller#valid(boolean, String, int[], Object...)}.
*/
final ArgValidationErrors returnValue;
/** Flag indicating whether signature of caller's parameters exactly matches method */
final boolean exactMatch;
/**
* Constructor.
*
* @param method
* Method, if any, else <code>null</code> if none was found.
* @param returnValue
* Enumerated return value for {@link
* InternalEntryCaller#valid(boolean, String, int[], Object...)}.
* @param exactMatch
* Flag indicating whether signature of caller's parameters exactly matches
* method.
*/
CacheValue(Method method, ArgValidationErrors returnValue, boolean exactMatch)
{
this.method = method;
if (method != null)
{
if (!Modifier.isPrivate(method.getModifiers()))
{
this.access = MethodAccess.get(method.getDeclaringClass());
this.index = access.getIndex(method.getName(), method.getParameterTypes());
}
else
{
this.access = null;
this.index = -1;
}
}
else
{
this.access = null;
this.index = -1;
}
this.returnValue = returnValue;
this.exactMatch = exactMatch;
}
}
}
/**
* This caller is returned when the FUNCTION ... MAP TO can not be resolved. It contains the
* function name from the last MAP TO clause.
*/
private static class MapToNotFound
extends InternalEntryCaller
{
public MapToNotFound(WorkArea wa, String iename)
{
super(wa);
this.iename = iename;
}
}
/**
* Implements native procedure invocation (an API call to a shared library).
*/
private static class NativeProcedureCaller
extends InternalEntryCaller
{
/** The library name where this native procedure belongs. */
private String libname = null;
/** The legacy name of this native procedure. */
private String pname = null;
/**
* Configure this caller with the given data.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param phandle
* The procedure handle where this external entry is defined.
* @param libname
* The library name where this native procedure should be found.
* @param pname
* The legacy name of the external procedure from which this API is being called.
* @param iename
* The internal-entry name being called which is usually the same as the entry
* point name in the library except where an ordinal is being used.
*/
public NativeProcedureCaller(WorkArea wa, handle phandle, String libname, String pname, String iename)
{
super(wa, phandle, null, iename);
this.iename = iename;
this.libname = libname;
this.pname = pname;
}
/**
* Call the native API with the arguments provided.
*
* @param modes
* An encoded string with the mode of each parameter specified as a
* character in the string.
* @param args
* The array of procedure parameters.
*
* @return Always <code>null</code> since procedures don't have a return value in the
* 4GL.
*/
@Override
Object invokeImpl(String modes, Object... args)
throws Exception
{
boolean failed = false;
OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
try
{
Class<?>[] signature = getSignature(args);
NativeInvoker.invoke(this.libname, this.pname, this.iename, signature, modes, args);
failed = wa.em.isPending();
}
catch (Exception exc)
{
failed = true;
throw exc;
}
finally
{
if (opa != null)
{
if (failed)
{
opa.abort();
}
else
{
opa.processAssignments(pname, false);
}
}
}
return null;
}
/**
* For native API calls, the validation is deferred to the actual time of the call since
* the 4GL does other parameter checking before the more traditional type and mode
* checking. Rather than place that logic here, the decision was made to keep all of
* that processing together in {@link NativeInvoker#invoke}.
*
* @param function
* <code>true</code> if this is a function call.
* @param modes
* The types of parameters: INPUT(I) / OUTPUT (O) / INPUT-OUTPUT (U).
* @param extentInfo
* An output parameter array that provides the caller with additional information
* about the extent sizes when they don't match. The first item is the size of the
* argument (provided extent), the second is the parameter (expected extent).
* @param param
* The arguments for this Java method call.
*
* @return Always returns {@link ArgValidationErrors#DEFER_VALIDATION}, which disables
* validation processing at this time.
*/
@Override
ArgValidationErrors valid(boolean function, String modes, int[] extentInfo, Object... param)
{
return ArgValidationErrors.DEFER_VALIDATION;
}
/**
* Get the java method name for this internal entry.
*
* @return Always the same as the legacy native function name. This is OK since
* this method is only used for error messages.
*/
@Override
String getMethodName()
{
return pname;
}
/**
* Get the legacy name of the external program where the execution will take place.
*
* @return See above.
*/
@Override
String getExternalProgramName()
{
return pname;
}
/**
* Get the class name of the external program to which this internal entry belongs.
*
* @return Always a nonsense class name. This is OK since this method is only used
* for error messages.
*/
@Override
String getClassName()
{
return "NATIVE_LIBRARY_CALL";
}
}
/**
* Implements external program invocation.
*/
private static class ExternalProcedureCaller
extends InternalEntryCaller
{
/** Cache of classes for faster retrieval than {@code Class.forName} alone */
private final static Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
/** A cache of constructor access objects for each class. */
private final static Map<String, ConstructorAccess<?>> ctorCache = new ConcurrentHashMap<>();
/** The class name associated with the external program. */
private String className;
/**
* Associate this caller with the specified external program instance.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param instance
* An instance of an external program.
*/
public ExternalProcedureCaller(WorkArea wa, Object instance)
{
super(wa);
this.methodName = "execute";
this.className = instance.getClass().getName();
this.callerInstance = instance;
}
/**
* Configure this caller with the given data.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param className
* The class name associated with the external program.
* @param persistent
* Flag indicating if this external program is being ran persistent.
*
* @throws Exception
* If the class can't be resolved.
*/
public ExternalProcedureCaller(WorkArea wa, String className, boolean persistent)
throws Exception
{
super(wa);
this.methodName = "execute";
this.className = className;
Class<?> cls = classCache.get(className);
if (cls == null)
{
cls = Class.forName(className);
classCache.putIfAbsent(className, cls);
}
wa.pm.setInstantingExternalProgramClass(cls);
ConstructorAccess<?> ctor = ctorCache.get(className);
if (ctor == null)
{
ctor = ConstructorAccess.get(cls);
ctorCache.putIfAbsent(className, ctor);
}
// activate collection of all undoable vars defined in this procedure; this flag will be
// unset only when the external program starts.
if (persistent)
{
wa.tm.trackUndoables();
}
this.callerInstance = ctor.newInstance();
}
@Override
String getClassName()
{
return className;
}
@Override
String getInternalEntryName()
{
return "";
}
@Override
String getParameterModes(boolean function)
{
String pname = ProcedureManager.getAbsoluteName(callerInstance);
return SourceNameMapper.getParameterModes(pname);
}
@Override
handle getPhandle()
{
// For external procedures, the phandle field is always null.
return null;
}
}
/**
* Abstract class. Provides APIs to determine the correct target of a
* function or procedure invocation statement.
*/
private static abstract class Resolver
{
/** The {@link WorkArea} instance. */
protected final WorkArea wa;
/**
* Create a new instance and associate the given WorkArea instance.
*
* @param wa
* The {@link WorkArea} instance.
*/
public Resolver(WorkArea wa)
{
this.wa = wa;
}
/**
* Based on the given data, determine the procedure handle to which the
* internal entry belongs and its java method name (or the external
* program to be invoked).
*
* @param phandle
* The procedure handle where this internal entry is defined.
* @param name
* The legacy name of the internal entry or external program.
* @param function
* <code>true</code> if need to search for functions.
* <code>false</code> if need to search for procedures.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param exports
* The list of allowed external procedures to be invoked with a RUN ... ON SERVER
* statement.
* @param persistent
* Flag indicating if this external program is being ran persistent.
*
* @return An {@link InternalEntryCaller caller} or null if not found.
*/
abstract InternalEntryCaller resolve(handle phandle,
String name,
boolean function,
boolean superCall,
character exports,
boolean persistent);
/**
* Based on the given data, determine the procedure handle to which the
* internal entry belongs and its java method name (or the external
* program to be invoked).
*
* @param phandle
* The procedure handle where this internal entry is defined.
* @param name
* The legacy name of the internal entry or external program.
* @param function
* <code>true</code> if need to search for functions.
* <code>false</code> if need to search for procedures.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param exports
* The list of allowed external procedures to be invoked with a RUN ... ON SERVER
* statement.
*
* @return An {@link InternalEntryCaller caller} or null if not found.
*/
InternalEntryCaller resolve(handle phandle,
String name,
boolean function,
boolean superCall,
character exports)
{
return resolve(phandle, name, function, superCall, exports, false);
}
}
/**
* Search through the handle's defined internal-entries for a match.
*/
private static class InternalResolver
extends Resolver
{
public InternalResolver(WorkArea wa)
{
super(wa);
}
@Override
public InternalEntryCaller resolve(handle phandle,
String ieName,
boolean function,
boolean superCall,
character exports,
boolean persistent)
{
String pname = ProcedureManager.getAbsoluteName(phandle.get());
InternalEntryCacheKey key = new InternalEntryCacheKey(pname, ieName, function);
InternalEntry ie = wa.cachedIEntries.get(key);
if (ie == InternalEntry.NULL)
{
return null;
}
if (ie == null)
{
ie = SourceNameMapper.getInternalEntry(pname, ieName, function);
}
wa.cachedIEntries.put(key, ie == null ? InternalEntry.NULL : ie);
if (ie == null)
{
return null;
}
if (ie.isPrivate())
{
// when resolving a 'IN SUPER' procedure/function or 'IN handle' function, then the
// access mode for the procedure/function must be honored:
// - if SOURCE-PROCEDURE is the same as the TARGET-PROCEDURE, any access mode can be
// used
// - otherwise, only PUBLIC access mode can be used.
// in a call context, before the invocation, SOURCE-PROCEDURE is always THIS-PROCEDURE
// and TARGET-PROCEDURE is the 'phandle' (where the internal entry is defined)
handle thisp = wa.pm.thisProcedure();
if (!CompareOps.equals(thisp, phandle))
{
return null;
}
}
InternalEntryCaller caller = null;
String libname = ie.getLibname();
String mapTo = ie.getMapTo();
if (libname != null)
{
// don't cache the result, native API call
wa.currentInvoke = null;
caller = overrideNativeCall(phandle, libname, pname, ieName, ie);
if (caller == null)
{
caller = new NativeProcedureCaller(wa, phandle, libname, pname, ieName);
}
}
else if (mapTo != null)
{
// go recursively
InternalEntryCaller res = resolve(phandle, mapTo, function, superCall, exports, false);
if (res == null)
{
// don't cache the result
wa.currentInvoke = null;
res = new MapToNotFound(wa, mapTo);
}
res.ie = ie;
return res;
}
else if (!ie.isSuper())
{
handle hcaller = phandle;
if (ie.isInHandle())
{
// don't cache the result, we can't track when the IN expression changes
wa.currentInvoke = null;
hcaller = wa.pm.getHandleForFunction(phandle, ieName);
}
caller = new InternalEntryCaller(wa, hcaller, ie.jname, ieName);
caller.ie = ie;
}
/*
else if (!superCall)
{
// IN SUPER functions and procedures are used only when this
// call is not from a RUN SUPER or SUPER call.
Resolver r = work.obtain().internalProcSuperResolver;
return r.resolve(phandle, ieName, function, superCall);
}*/
return caller;
}
/** Holds data of cached procedure internal entries */
public static class InternalEntryCacheKey
{
/** Procedure name */
public String procName;
/** Internal entry name */
public String entryName;
/** Function flag */
public boolean function;
/** Hash code */
public int hashCode;
/**
* Constructor.
*
* @param procName
* Procedure name.
* @param entryName
* Internal entry name.
* @param function
* Function flag.
*/
public InternalEntryCacheKey(String procName, String entryName, boolean function)
{
this.procName = procName;
this.entryName = entryName;
this.function = function;
int result = 17;
result = 37 * result + (function ? 5 : 3);
result = 37 * result + Objects.hashCode(procName);
result = 37 * result + Objects.hashCode(entryName);
this.hashCode = result;
}
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link HashMap}.
*
* @return a hash code value for this object.
*/
@Override
public int hashCode()
{
return hashCode;
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param obj
* the reference object with which to compare.
*
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
*/
@Override
public boolean equals(Object obj)
{
InternalEntryCacheKey other = (InternalEntryCacheKey) obj;
if (other == null)
{
return false;
}
return other == this ||
other.function == function && Objects.equals(procName, other.procName) &&
Objects.equals(entryName, other.entryName);
}
}
}
/**
* Search through the given super-procedure list for a match.
*/
private static abstract class SuperResolver
extends InternalResolver
{
public SuperResolver(WorkArea wa)
{
super(wa);
}
private final Set<Object> used = Collections.newSetFromMap(new IdentityHashMap<>());
protected InternalEntryCaller resolveSuperProc(handle phandle,
List<handle> superProcs,
String ieName,
boolean function,
boolean superCall,
character exports)
{
// search is done backwards (last set super-procedure is searched
// first)
for (handle sphandle : superProcs)
{
Object referent = sphandle.get();
if (used.contains(referent))
{
continue;
}
used.add(referent);
try
{
// when looking for an internal-entry, only the direct super-
// procedures are searched. no recursivity is done.
InternalEntryCaller caller = super.resolve(sphandle,
ieName,
function,
superCall,
exports,
false);
if (caller != null)
{
Object callerRef = caller.phandle.get();
if (callerRef != referent && (used.contains(callerRef) || callerRef == phandle.get()))
{
// we moved into another program, if this program is the source reference or one of the
// used super-procedure, do not allow it.
caller = null;
}
}
if (caller != null)
{
return caller;
}
}
finally
{
used.remove(referent);
}
}
return null;
}
}
/**
* Search through the super-procedures of the given procedure handle for
* a match.
*/
private static class InternalProcSuperResolver
extends SuperResolver
{
public InternalProcSuperResolver(WorkArea wa)
{
super(wa);
}
@Override
public InternalEntryCaller resolve(handle phandle,
String ieName,
boolean function,
boolean superCall,
character exports,
boolean persistent)
{
List<handle> superProcs = wa.pm.getSuperProcedures(phandle.get());
return resolveSuperProc(phandle, superProcs, ieName, function, superCall, exports);
}
}
/**
* Search through the session's super-procedures of the handle for a match.
*/
private static class SessionSuperResolver
extends SuperResolver
{
public SessionSuperResolver(WorkArea wa)
{
super(wa);
}
@Override
public InternalEntryCaller resolve(handle phandle,
String ieName,
boolean function,
boolean superCall,
character exports,
boolean persistent)
{
List<handle> superProcs = wa.pm.getSuperProcedures();
return resolveSuperProc(phandle, superProcs, ieName, function, superCall, exports);
}
}
/**
* Search through the available external programs for a match.
*/
private static class ExternalProgramResolver
extends Resolver
{
/** The already resolved class name, from a previous cached call. */
private String className = null;
public ExternalProgramResolver(WorkArea wa)
{
super(wa);
}
@Override
public InternalEntryCaller resolve(handle phandle,
String name,
boolean function,
boolean superCall,
character exports,
boolean persistent)
{
if (function)
{
// for functions, don't search external procedures
return null;
}
if (exports != null &&
!exports.isUnknown() &&
!TextOps.matchesList(exports, name).booleanValue())
{
// in case this is call is via an appserver, only the exported procedures can be used
return null;
}
name = SourceNameMapper.normalizeLegacyName(name);
String className = this.className;
if (className == null)
{
className = SourceNameMapper.convertNameToClass(name);
}
else
{
// reset, no longer needed
this.className = null;
}
InternalEntryCaller caller = null;
if (className != null)
{
// is a registered external procedure
try
{
// set the 4GL ext prog being instantiated; this is required to expose the 4GL prog
// name to any errors generated during instantiation (i.e. shared vars error msgs
// need to know the procedure name where they are defined, if an error happens)
wa.pm.setInstantingExternalProgram(name);
caller = new ExternalProcedureCaller(wa, className, persistent);
}
catch (Throwable t)
{
cleanupPending(wa);
Throwable cause = t;
while (cause != null)
{
// if the exception is a condition exception, let it propagate up the stack
if (cause instanceof ErrorConditionException)
{
// ERROR conditions raised during instantiation can be either logged
// (if NO-ERROR is in effect) or re-thrown. Let the ErrorManager decide
// what to do; in any case, this returns null
ErrorConditionException ece = (ErrorConditionException) cause;
String msg = ece.getMessage();
int num = ece.getProgressErrorCode();
ErrorManager.recordOrThrowError(num, msg, false);
return null;
}
else if (cause instanceof ConditionException)
{
throw (ConditionException) cause;
}
if (cause instanceof RetryUnwindException)
{
throw (RetryUnwindException) cause;
}
cause = cause.getCause();
}
// if the exception was not propagated, log it, as something severe had happened.
LOG.log(Level.SEVERE, "Unable to resolve external program.", t);
return null;
}
finally
{
wa.pm.setInstantingExternalProgram(null);
wa.pm.setInstantingExternalProgramClass(null);
}
}
return caller;
}
}
/**
* An interface for which its implementation will resolve the arguments passed to a
* <code>ControlFlowOps.invoke</code> API.
*/
private static interface ArgumentResolver
{
/**
* Resolve the arguments.
*
* @param caller
* The resolved caller, to which the arguments will be passed.
* @param pname
* The caller's legacy name.
* @param function
* Flag indicating if this is a 4GL function call (when <code>true</code>).
*
* @return An array with the resolved arguments.
*/
public Object[] resolve(InternalEntryCaller caller, String pname, boolean function);
}
/**
* This implementation resolves arguments for converted 4GL statements (RUN, function calls,
* DYNAMIC-FUNCTION). It just passes back the received array.
*/
private static class ArrayArgumentResolver
implements ArgumentResolver
{
/** The arguments used for this 4GL-style call. */
private final Object[] args;
/**
* Create a new instance and save the arguments.
*
* @param args
* The arguments for this 4GL-style call.
*/
public ArrayArgumentResolver(Object[] args)
{
this.args = args;
}
/**
* Resolve the arguments by returning back the {@link #args} received on instantiation.
*
* @param caller
* The resolved caller, to which the arguments will be passed.
* @param pname
* The caller's legacy name.
* @param function
* Flag indicating if this is a 4GL function call (when <code>true</code>).
*
* @return The initial {@link #args}.
*/
@Override
public Object[] resolve(InternalEntryCaller caller, String pname, boolean function)
{
Object[] res = new Object[args.length];
for (int i = 0; i < args.length; i++)
{
if (args[i] instanceof BaseDataType)
{
// the value may be a proxy; make sure to unwrap and work with the real value
res[i] = ((BaseDataType) args[i]).val();
}
else
{
res[i] = args[i];
}
}
return res;
}
}
/**
* This is a special implementation of resolving arguments received in a {@link Map}, where
* the key is the legacy argument's name (case insensitive) and the value is a string-encoded
* representation of this argument.
* <p>
* When resolving the arguments, they are matched against the caller's signature and an array
* is constructed, so that the argument order matches the caller's signature. If an argument
* is not specified in the {@link #args map}, an unknown value is assumed for it.
*/
private static class MapArgumentResolver
implements ArgumentResolver
{
/**
* A map with string representation of the 4GL supported types to their P2J implementation
* class.
*/
private static final Map<String, Class<? extends BaseDataType>> SUPPORTED_TYPES;
static
{
Map<String, Class<? extends BaseDataType>> types = new HashMap<>();
types.put("RAW", raw.class);
types.put("DATE", date.class);
types.put("DATETIME", datetime.class);
types.put("DATETIMETZ", datetimetz.class);
types.put("DATETIME-TZ", datetimetz.class);
types.put("LOGICAL", logical.class);
types.put("DECIMAL", decimal.class);
types.put("INT64", int64.class);
types.put("INTEGER", integer.class);
types.put("RECID", recid.class);
types.put("ROWID", rowid.class);
types.put("CHARACTER", character.class);
types.put("LONGCHAR", longchar.class);
SUPPORTED_TYPES = Collections.unmodifiableMap(types);
}
/** The argument map. */
private final Map<String, String> args;
/** The {@link #args} converted/casted to their associated P2J type. */
private final Map<String, BaseDataType> resolvedArgs = new HashMap<>();
/** A set of 4GL legacy parameter names, which are marked as OUTPUT or INPUT-OUTPUT. */
private Set<String> outputArguments = new HashSet<>();
/**
* Create a new instance.
*
* @param args
* The map of arguments.
*/
public MapArgumentResolver(Map<String, String> args)
{
this.args = new HashMap<>();
for (String key : args.keySet())
{
this.args.put(key.toLowerCase(), args.get(key));
}
}
/**
* Resolve the arguments which will be passed to the caller.
*
* @param caller
* The resolved caller, to which the arguments will be passed.
* @param name
* The caller's legacy name.
* @param function
* Flag indicating if this is a 4GL function call (when <code>true</code>).
*
* @return The resolved arguments.
*
* @throws ErrorConditionException
* If the arguments can't be matched against the caller's signature (by number,
* name or type).
*/
@Override
public Object[] resolve(InternalEntryCaller caller, String name, boolean function)
{
List<Parameter> params = caller.getParameters(function);
if (params == null || params.isEmpty())
{
// no parameters, check if the arg list is empty!
if (!args.isEmpty())
{
// TODO: this has to do with the MSG_P2J_INVOKE incoming from the JS side, in
// embedded mode; this is a FWD feature with no 4GL counterpart, so we can
// change it to use recordOrThrowError(), as long as the remote JS side is
// properly informed of the error; this code was originally adapted from
// ControlFlowOps.validArguments
String msg = "Remote invoke passed parameters to %s, which did not expect any";
msg = String.format(msg, name);
throw new ErrorConditionException(1005, msg);
}
return new Object[0];
}
Object[] res = new Object[params.size()];
Set<String> unused = new HashSet<>();
unused.addAll(args.keySet());
String modes = caller.getParameterModes(function);
for (int i = 0; i < modes.length(); i++)
{
Parameter param = params.get(i);
String pname = param.getLegacyName().toLowerCase();
String ptype = param.getType();
if (param.isExtent())
{
// TODO: this is not safe for silent error mode, ErrorManager.recordOrThrowError()
// should be used OR ErrorManager.throwError(NE, boolean) but generally we
// should not be instantiating this exception directly
String msg = "Argument %s with type %s is defined EXTENT, which is not supported.";
throw new ErrorConditionException(5729, String.format(msg, pname, ptype));
}
if (modes.charAt(i) != 'I')
{
outputArguments.add(pname);
}
Class<? extends BaseDataType> pclass = SUPPORTED_TYPES.get(ptype.toUpperCase());
if (pclass == null)
{
// TODO: this is not safe for silent error mode, ErrorManager.recordOrThrowError()
// should be used OR ErrorManager.throwError(NE, boolean) but generally we
// should not be instantiating this exception directly
String msg = "Argument %s has an unsupported type: %s";
throw new ErrorConditionException(5729, String.format(msg, pname, ptype));
}
// support only BDT sub-classes (check ptype against a list of BDT sub-classes)
String sval = args.get(pname);
BaseDataType aval = null;
if (sval == null)
{
// build a new instance, based on parameter's type
aval = BaseDataType.generateUnknown(pclass);
}
else
{
Runnable error = () ->
{
// raise error, could not convert!
String msg = "Remote called routine %s %s has mismatched parameters";
msg = String.format(msg, caller.getInternalEntryName(), name);
// TODO: this is not safe for silent error mode, recordOrThrowError()
// should be used OR throwError(NE, boolean) but generally we
// should not be instantiating this exception directly
throw new ErrorConditionException(5729, msg);
};
try
{
// build a new instance using the BaseDataType(String) c'tor
aval = pclass.getConstructor(String.class).newInstance(sval);
}
catch (Exception expt)
{
error.run();
}
if (caller.wa.em.isPending())
{
error.run();
}
}
// this argument is used, save it
unused.remove(pname);
res[i] = aval;
resolvedArgs.put(pname, aval);
}
if (!unused.isEmpty())
{
// TODO: this is not safe for silent error mode, ErrorManager.recordOrThrowError()
// should be used OR ErrorManager.throwError(NE, boolean) but generally we
// should not be instantiating this exception directly
// some arguments were received which couldn't be matched - raise ERROR
String msg = "Mismatched number of parameters passed to routine %s %s: %s";
msg = String.format(msg, caller.getInternalEntryName(), name, unused.toString());
throw new ErrorConditionException(3234, msg);
}
return res;
}
/**
* Get a map with the OUTPUT or INPUT-OUTPUT arguments, with their state as after the caller
* has been executed. The keys will be the legacy 4GL names and the values will be a string
* representation of each OUTPUT or INPUT-OUTPUT argument.
*
* @return See above.
*/
public Map<String, String> getOutputArguments()
{
Map<String, String> res = new HashMap<>();
for (String key : resolvedArgs.keySet())
{
if (outputArguments.contains(key))
{
res.put(key, resolvedArgs.get(key).toStringMessage());
}
}
return res;
}
}
/**
* Helper class which also saves the {@link AsyncRequestImpl} instance, needed by the remote
* request.
*/
private static abstract class AsyncCall
implements Runnable
{
/** The async request instance for this call. */
public final AsyncRequestImpl asyncReq;
/** The server used to execute this async call. */
public final handle hServer;
/** A handle variable where to save the web service. May be null. */
public final handle portHandle;
/** The target procedure name. */
public final character pName;
/** The handle to which the procedure belongs, if it is RUN ... IN handle statement. */
public final handle pHandle;
/** The handle to the asynchronous request. */
public final handle asyncHandle;
/** This represents the name of the internal procedure for the EVENT-PROCEDURE clause. */
public final character eventProcName;
/** The handle to a procedure in which context the EVENT-PROCEDURE is found. */
public final handle inEventprocHandle;
/**
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON statement.
*/
public final boolean transactionDistinct;
/**
*
* @param hServer
* The server used to execute this async call.
* @param portHandle
* A handle variable where to save the web service. May be null.
* @param pName
* The target procedure name.
* @param pHandle
* The handle to which the procedure belongs, if it is RUN ... IN handle
* statement.
* @param asyncHandle
* The handle to the asynchronous request.
* @param eventProcName
* This represents the name of the internal procedure for the EVENT-PROCEDURE
* clause, null if this clause is not specified.
* @param inEventprocHandle
* The handle to a procedure in which context the specified EVENT-PROCEDURE internal
* procedure is found, null if EVENT-PROCEDURE clause is not specified.
* @param transactionDistinct
* This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON
* statement.
* @param modes
* A string representation of the modes of each parameter.
* @param args
* The procedure's arguments.
*/
public AsyncCall(handle hServer,
handle portHandle,
character pName,
handle pHandle,
handle asyncHandle,
character eventProcName,
handle inEventprocHandle,
boolean transactionDistinct,
String modes,
Object... args)
{
this.hServer = new handle(hServer);
this.portHandle = portHandle == null ? null : new handle(portHandle);
this.pName = new character(pName);
this.pHandle = pHandle == null ? null : new handle(pHandle);
this.asyncHandle = asyncHandle == null ? null : new handle(asyncHandle);
this.eventProcName = eventProcName == null ? null : new character(eventProcName);
this.inEventprocHandle = inEventprocHandle == null ? null : new handle(inEventprocHandle);
this.transactionDistinct = transactionDistinct;
this.asyncReq = new AsyncRequestImpl(this.hServer,
this,
this.eventProcName,
this.inEventprocHandle,
this.pName,
this.pHandle,
modes,
args);
}
}
}