CoreAppserver.java
/*
** Module : CoreAppserver.java
** Abstract : Core appserver reusable logic.
**
** Copyright (c) 2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 GBB 20250122 Methods moved from Agent to CoreAppserver to be reused by the new MSA appserver.
*/
/*
** 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.appserver;
import com.goldencode.p2j.*;
import com.goldencode.p2j.main.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.oo.web.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.trigger.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import javax.servlet.http.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
/** Core appserver reusable logic. */
public class CoreAppserver
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(CoreAppserver.class);
/**
* Initial setup for an appserver session.
*
* @param isServerSideMsa
* Flag if the session is for multi-session agent or classic agent.
* @param appserverName
* The appserver name.
* @param searchPath
* The search path.
* @param topScopeLabel
* The label of the top scope.
*/
public static void prepareSession(boolean isServerSideMsa,
String appserverName,
String searchPath,
String topScopeLabel)
{
// set this as batch mode
EnvironmentOps.setBatchMode(true);
if (!isServerSideMsa)
{
LogicalTerminal.activateBatchMode(true);
}
// set the propath
EnvironmentOps.setSearchPath(searchPath);
// force DatabaseTriggerManager context-local instantiation
DatabaseTriggerManager.get();
ProcedureManager.load();
try
{
LegacyLogManagerConfigs legacyLogManagerConfigs =
LegacyLogOps.appserverNameLogConfigPairs.get(appserverName);
LegacyLogOps.logMgr().initialize(legacyLogManagerConfigs);
}
catch (Throwable t)
{
LOG.log(Level.WARNING, t, "LOG-MANAGER couldn't be initialized for appserver %s.", appserverName);
}
// Global scope for keeping buffers states (like BufferManager.allBuffers). This scope must be
// explicitly popped when the agent context gets reset or when the agent terminates, so the dynamic
// DMOs and any other global finalizables must be processed.
TransactionManager.pushScope(topScopeLabel,
TransactionManager.NO_TRANSACTION,
true,
true,
false,
false);
if (!isServerSideMsa)
{
// always null stream
UnnamedStreams.assignOut(StreamFactory.openNullStream());
}
DatabaseManager.autoConnect();
}
/**
* Invoke a remote external procedure, internal procedure or user-defined function. The
* validation for the program name and arguments will be done on the remote side.
*
* @param invocationResult
* The result where to save the arguments and any return value.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
* @param isNewMultiSession
* Flag to indicate running in the new version of the multi-session agent appserver.
* @param exports
* The export list for remote procedures the client can execute in the current session.
* @param activateProc
* The session activate procedure.
* @param deactivateProc
* The session deactivate procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param connectionId
* The appserver connection id.
* @param agentId
* The agent id.
* @param threadId
* The thread id.
* @param sessionId
* The appserver session id.
* @param cfg
* The invoke configuration.
* @param inProcResId
* The resource id of the procedure.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param removeProcedureFunc
* The function removing the procedure.
* @param disconnectFunc
* The function disconnecting the client.
*/
public static void invoke(AppServerInvocationResult invocationResult,
boolean isMultiSession,
boolean isNewMultiSession,
character exports,
String activateProc,
String deactivateProc,
Supplier<Boolean> isBoundSupplier,
String connectionId,
int agentId,
Integer threadId,
Integer sessionId,
InvokeConfig cfg,
String inProcResId,
BiConsumer<String, String> setRunningProcFunc,
BiConsumer<String, String> removeProcedureFunc,
Consumer<String> disconnectFunc)
{
setOeRequestInfo(cfg, isMultiSession, agentId, threadId, sessionId);
if (!invokeActivate(activateProc, deactivateProc, isBoundSupplier, connectionId, invocationResult,
setRunningProcFunc, disconnectFunc))
{
return;
}
// resolve the procedure
handle inHandle = cfg.getInHandle();
String resId = null;
String runningProgram = null;
if (cfg.isSingleRun() || cfg.isSingleton())
{
DeferredProgram deferred = (DeferredProgram) inHandle.get();
deferred.setSingleRun(cfg.isSingleRun());
deferred.setSingleton(cfg.isSingleton());
try
{
inHandle = (handle) executeScoped(() -> ControlFlowOps.resolveInstance(deferred, cfg.isClass()));
}
catch (ConditionException exc)
{
invocationResult.setError(exc);
}
catch (Throwable t)
{
LOG.severe("", t);
invocationResult.setError(t);
}
if (inHandle == null || !inHandle._isValid())
{
return;
}
runningProgram = deferred.getName();
}
else if (inHandle != null || inProcResId != null)
{
resId = inProcResId != null ?
inProcResId :
((ProxyProcedureWrapper) inHandle.getResource()).getResourceId();
// resolve the procedure - this is actually 'extProg', validated when the agent was acquired.
inHandle = handle.fromString(resId);
runningProgram = resId;
}
runningProgram = runningProgram == null ? cfg.getTarget() : runningProgram + " / " + cfg.getTarget();
setRunningProcFunc.accept(connectionId, runningProgram);
boolean isQuit = false;
try
{
handle finHandle = inHandle;
Object[] args = cfg.getArguments() == null ? new Object[0] : cfg.getArguments();
if (cfg.isClass())
{
executeScoped(() ->
{
invokeMethodImpl(invocationResult,
isMultiSession,
new character(cfg.getTarget()),
new object(((ObjectResource) finHandle.getResource()).ref()),
cfg.isReturnValue(),
cfg.getModes(),
args);
return null;
});
}
else
{
isQuit = ((logical) executeScoped(() ->
{
return new logical(invokeImpl(invocationResult,
isMultiSession,
exports,
new character(cfg.getTarget()),
finHandle,
cfg.isFunction(),
cfg.isDynamicFunction(),
cfg.isSuperCall(),
cfg.isTransactionDistinct(),
cfg.getModes(),
args));
})).booleanValue();
}
}
finally
{
if (cfg.isSingleRun())
{
ProcedureManager.deleteInstance(inHandle, cfg.isClass());
}
if (resId != null && !inHandle._isValid())
{
invocationResult.setInvalidInHandle(true);
removeProcedureFunc.accept(connectionId, resId);
}
else
{
invokeDeactivate(deactivateProc, isBoundSupplier.get(), connectionId, setRunningProcFunc);
}
if (isQuit && !isNewMultiSession)
{
disconnectFunc.accept(connectionId);
}
}
}
/**
* Invoke a persistent procedure.
*
* @param invocationResult
* The result where to save the arguments and any return value.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
* @param exports
* The export list for remote procedures the client can execute in the current session.
* @param activateProc
* The session activate procedure.
* @param deactivateProc
* The session deactivate procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param connectionId
* The appserver connection id.
* @param agentId
* The agent id.
* @param threadId
* The thread id.
* @param sessionId
* The appserver session id.
* @param cfg
* The invoke configuration.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param bindPersistentFunc
* The function binding the persistent procedure.
* @param unbindPersistentFunc
* The function unbinding the persistent procedure.
* @param addProcedureFunc
* The function adding the persistent procedure.
* @param disconnectFunc
* The function disconnecting the client.
*/
public static void runPersistent(AppServerInvocationResult invocationResult,
boolean isMultiSession,
character exports,
String activateProc,
String deactivateProc,
Supplier<Boolean> isBoundSupplier,
String connectionId,
int agentId,
Integer threadId,
Integer sessionId,
InvokeConfig cfg,
BiConsumer<String, String> setRunningProcFunc,
BiConsumer<String, Integer> bindPersistentFunc,
BiConsumer<String, Integer> unbindPersistentFunc,
BiConsumer<String, handle> addProcedureFunc,
Consumer<String> disconnectFunc)
{
setOeRequestInfo(cfg, isMultiSession, agentId, threadId, sessionId);
if (!invokeActivate(activateProc,
deactivateProc,
isBoundSupplier,
connectionId,
invocationResult,
setRunningProcFunc,
disconnectFunc))
{
return;
}
Object[] args = cfg.getArguments() == null ? new Object[0] : cfg.getArguments();
invokePersistentImpl(invocationResult,
connectionId,
agentId,
sessionId,
new character(cfg.getTarget()),
cfg.getProcedureHandle(),
cfg.isTransactionDistinct(),
exports,
setRunningProcFunc,
bindPersistentFunc,
unbindPersistentFunc,
addProcedureFunc,
cfg.getModes(),
args);
// called when the procedure file is missing
invokeDeactivate(deactivateProc, isBoundSupplier.get(), connectionId, setRunningProcFunc);
}
/**
* Invoke the activate procedure on the specified connection ID.
*
* @param activateProc
* The session activate procedure.
* @param deactivateProc
* The session deactivate procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param connectionId
* The appserver connection id.
* @param invocationResult
* The result where to save the arguments and any return value.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param disconnectFunc
* The function disconnecting the client.
*
* @return <code>true</code> if the activate procedure is successfully executed, or if it's not
* applicable. <code>false</code> if there was an issue with procedure execution.
*/
@SuppressWarnings("incomplete-switch")
public static boolean invokeActivate(String activateProc,
String deactivateProc,
Supplier<Boolean> isBoundSupplier,
String connectionId,
AppServerInvocationResult invocationResult,
BiConsumer<String, String> setRunningProcFunc,
Consumer<String> disconnectFunc)
{
if (isBoundSupplier.get() || activateProc == null || activateProc.length() == 0)
{
return true;
}
setRunningProcFunc.accept(connectionId, activateProc);
// call the activate procedure (only if the agent is not already bound)
try
{
invokeScoped(new character(activateProc));
}
catch (ConditionException ce)
{
BlockManager.Condition c = AppserverPackageExports.exceptionToCondition(ce);
String type = c.toString();
int code = 0;
switch (c)
{
case ERROR:
code = 8025;
break;
case STOP:
code = 8026;
break;
case QUIT:
code = 8027;
break;
}
final String msg = "%s activation procedure ended with an %s condition";
invocationResult.setError(new NumberedException(String.format(msg, activateProc, type), code));
return false;
}
catch (Throwable t)
{
boolean ignore = false;
Throwable cause = t;
while (cause != null)
{
if (cause instanceof UnstoppableExitException)
{
LogicalTerminal.activateBatchMode(true);
ignore = true;
break;
}
cause = cause.getCause();
}
if (!ignore)
{
invocationResult.setError(t);
return false;
}
}
finally
{
// pass return value
if (ControlFlowOps.isReturnValueSetInRootScope())
{
invocationResult.setResult(ControlFlowOps.getReturnValue());
}
// if an error was encountered during activate, call deactivate and disconnect
if (invocationResult.getError() != null)
{
setRunningProcFunc.accept(connectionId, deactivateProc);
if (!isBoundSupplier.get() && deactivateProc != null && deactivateProc.length() > 0)
{
// call the deactivate procedure (only if the agent is not bound)
try
{
invokeScoped(new character(deactivateProc));
}
catch (Throwable t)
{
// ignore all errors
}
}
disconnectFunc.accept(connectionId);
}
setRunningProcFunc.accept(connectionId, null);
}
return true;
}
/**
* Invoke the deactivate procedure on the specified connection ID.
*
* @param deactivateProc
* The session deactivate procedure.
* @param isBound
* True if the connection is bound to the appserver session.
* @param connectionId
* The appserver connection id.
* @param setRunningProcFunc
* The function setting the running procedure.
*/
public static void invokeDeactivate(String deactivateProc,
boolean isBound,
String connectionId,
BiConsumer<String, String> setRunningProcFunc)
{
if (!isBound && deactivateProc != null && deactivateProc.length() > 0)
{
setRunningProcFunc.accept(connectionId, deactivateProc);
// call the deactivate procedure (only if the agent is not bound)
try
{
invokeScoped(new character(deactivateProc));
}
catch (Throwable t)
{
// ignore all errors
}
finally
{
setRunningProcFunc.accept(connectionId, null);
}
}
}
/**
* Invoke an external procedure, internal procedure or user-defined function.
*
* @param invocationResult
* The result where to save the arguments and any return value.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
* @param exports
* The export list for remote procedures the client can execute in the current session.
* @param name
* The legacy 4GL name for the procedure/function.
* @param inProc
* The external procedure in which to invoke the procedure or 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
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return <core>true</core> if QUIT condition returned, expecting disconnect.
*/
public static boolean invokeImpl(AppServerInvocationResult invocationResult,
boolean isMultiSession,
character exports,
character name,
handle inProc,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
String modes,
Object... args)
{
boolean isQuit = false;
// make a copy of the requester's args
final Object[] requesterArgs = new Object[args.length];
System.arraycopy(args, 0, requesterArgs, 0, requesterArgs.length);
try
{
preProcessArguments(modes, args);
Object result = invocationResult.getResult(); // value could be set in appserver activate procedure
if (inProc == null)
{
// force invocation of an external procedure
ControlFlowOps.invokeExternalProcedure(name, false, null, transactionDistinct,
exports, modes, args);
if (ControlFlowOps.isReturnValueSetInRootScope())
{
result = ControlFlowOps.getReturnValue();
}
}
else
{
// this is an internal-entry call
result = ControlFlowOps.invoke(inProc, name, function, dynamicFunction, superCall,
transactionDistinct, modes, args);
if (!function)
{
result = ControlFlowOps.isReturnValueSetInRootScope() ? ControlFlowOps.getReturnValue() : null;
}
}
if (result instanceof handle)
{
// handle results are converted to a container holding only the remote resource
handle h = (handle) result;
result = new RemoteResourceImpl(h.isUnknown() ? null : h.toStringMessage());
}
invocationResult.setResult(result);
}
catch (ErrorConditionException e)
{
checkError(invocationResult, e, isMultiSession);
}
catch (QuitConditionException e)
{
invocationResult.setError(e);
isQuit = true;
}
catch (StopConditionException e)
{
if (invokeFailure(e))
{
return false;
}
throw e;
}
catch (Throwable t)
{
boolean ignore = false;
Throwable cause = t;
while (cause != null)
{
if (cause instanceof UnstoppableExitException)
{
LogicalTerminal.activateBatchMode(true);
ignore = true;
break;
}
cause = cause.getCause();
}
if (!ignore)
{
invocationResult.setError(t);
}
}
finally
{
Object[] finalArgs = postProcessArguments(modes, args, requesterArgs);
invocationResult.setArguments(finalArgs);
invocationResult.setModes(modes);
}
return isQuit;
}
/**
* Invoke a legacy OO method.
*
* @param invocationResult
* The result where to save the arguments and any return value.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
* @param name
* The legacy 4GL name for the OO method.
* @param ref
* The object where to invoke the method.
* @param returnValue
* Flag indicating if this OO method call must return a value.
* @param modes
* The method argument's modes, at the caller.
* @param args
* The arguments.
*/
private static void invokeMethodImpl(AppServerInvocationResult invocationResult,
boolean isMultiSession,
character name,
object<?> ref,
boolean returnValue,
String modes,
Object... args)
{
// make a copy of the requester's args
final Object[] requesterArgs = new Object[args.length];
System.arraycopy(args, 0, requesterArgs, 0, requesterArgs.length);
try
{
preProcessArguments(modes, args);
try
{
Object result = null;
if (returnValue)
{
result = ObjectOps.invoke(ref, name, modes, args);
if (result instanceof handle)
{
// handle results are converted to a container holding only the remote resource
handle h = (handle) result;
result = new RemoteResourceImpl(h.isUnknown() ? null : h.toStringMessage());
}
invocationResult.setResult(result);
}
else
{
ObjectOps.invokeStandalone(ref, name, modes, args);
}
}
catch (StopConditionException e)
{
throw e;
}
finally
{
Object[] finalArgs = postProcessArguments(modes, args, requesterArgs);
invocationResult.setArguments(finalArgs);
invocationResult.setModes(modes);
}
}
catch (ErrorConditionException e)
{
checkError(invocationResult, e, isMultiSession);
}
catch (QuitConditionException e)
{
invocationResult.setError(e);
}
catch (Throwable t)
{
boolean ignore = false;
Throwable cause = t;
while (cause != null)
{
if (cause instanceof UnstoppableExitException)
{
LogicalTerminal.activateBatchMode(true);
ignore = true;
break;
}
cause = cause.getCause();
}
if (!ignore)
{
invocationResult.setError(t);
}
}
}
/**
* Invoke a remote external procedure, persistently. The validation of the program name and
* arguments will be done on the remote side.
*
* @param invocationResult
* The result to be sent back to the remote side.
* @param connectionId
* The appserver connection id.
* @param agentId
* The agent id.
* @param sessionId
* The appserver session id.
* @param name
* The legacy 4GL name for the procedure.
* @param hProc
* The handle where to save the persistent procedure.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param exports
* The export list for remote procedures the client can execute in the current session.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param bindPersistentFunc
* The function binding the persistent procedure.
* @param unbindPersistentFunc
* The function unbinding the persistent procedure.
* @param addProcedureFunc
* The function adding the persistent procedure.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*/
public static void invokePersistentImpl(AppServerInvocationResult invocationResult,
String connectionId,
Integer agentId,
Integer sessionId,
character name,
handle hProc,
boolean transactionDistinct,
character exports,
BiConsumer<String, String> setRunningProcFunc,
BiConsumer<String, Integer> bindPersistentFunc,
BiConsumer<String, Integer> unbindPersistentFunc,
BiConsumer<String, handle> addProcedureFunc,
String modes,
Object... args)
{
executeScoped(() ->
{
bindPersistentFunc.accept(connectionId, sessionId);
setRunningProcFunc.accept(connectionId, name == null ? null : name.toStringMessage());
// make a copy of the requester's args
Object[] requesterArgs = new Object[args.length];
System.arraycopy(args, 0, requesterArgs, 0, requesterArgs.length);
try
{
preProcessArguments(modes, args);
ControlFlowOps
.invokeExternalProcedure(name, true, hProc, transactionDistinct, exports, modes, args);
}
catch (Throwable t)
{
unbindPersistentFunc.accept(connectionId, sessionId);
if (t instanceof StopConditionException)
{
if (invokeFailure((StopConditionException) t))
{
return null;
}
}
throw t;
}
finally
{
Object[] finalArgs = postProcessArguments(modes, args, requesterArgs);
invocationResult.setArguments(finalArgs);
invocationResult.setModes(modes);
}
// set the agent's procedure
addProcedureFunc.accept(connectionId, hProc);
if (ControlFlowOps.isReturnValueSetInRootScope())
{
invocationResult.setResult(ControlFlowOps.getReturnValue());
}
invocationResult.setProcedureId(hProc.toString());
invocationResult.setAgentId(agentId);
return null;
});
}
/**
* Perform a pseudo-dynamic call where the target procedure doesn't receive the parameter as arguments at
* the Java method definition, but instead they are managed via {@link LegacyOpenClientCaller} APIs.
* <p>
* This allows the target program to act as a controller, where it can prepare the arguments, perform
* security checks, etc, before dispatching the call to the real target, which can be resolved from the
* arguments or in some other way.
*
* @param invocationResult
* The result to be sent back to the remote side.
* @param connectionId
* The appserver connection id.
* @param activateProc
* The session activate procedure.
* @param deactivateProc
* The session deactivate procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param disconnectFunc
* The function disconnecting the client.
* @param procedure
* The target external program. Must have no parameters defined.
* @param argTypes
* The argument types, one of the e.g. {@link LegacyJavaAppserverApi#integer()} values.
* @param modes
* The argument modes, one of the (I)NPUT, (O)UTPUT or INP(U)T-OUTPUT values.
* @param args
* The actual arguments.
*/
public static void invokeWithArgs(AppServerInvocationResult invocationResult,
String connectionId,
String activateProc,
String deactivateProc,
Supplier<Boolean> isBoundSupplier,
BiConsumer<String, String> setRunningProcFunc,
Consumer<String> disconnectFunc,
String procedure,
int[] argTypes,
String modes,
Object[] args)
{
if (!invokeActivate(activateProc, deactivateProc, isBoundSupplier, connectionId, invocationResult,
setRunningProcFunc, disconnectFunc))
{
return;
}
setRunningProcFunc.accept(connectionId, procedure);
// make a copy of the requester's args
Object[] requesterArgs = new Object[args.length];
System.arraycopy(args, 0, requesterArgs, 0, requesterArgs.length);
// add the topmost scope to the TransactionManager
TransactionManager.pushScope("startup",
TransactionManager.NO_TRANSACTION,
true,
true,
false,
false);
try
{
preProcessArguments(modes, args);
// initialize the arguments - after they were preprocessed
AppserverPackageExports.initializeOpenClientArguments(argTypes, modes, args);
ControlFlowOps.invokeExternalProcedure(new character(procedure), false, null, false, null, null);
}
catch (StopConditionException e)
{
if (invokeFailure(e))
{
return;
}
throw e;
}
finally
{
Object[] finalArgs = postProcessArguments(modes, args, requesterArgs);
invocationResult.setArguments(finalArgs);
invocationResult.setModes(modes);
AppserverPackageExports.clearOpenClientArguments();
// remove the topmost scope from the TransactionManager
TransactionManager.popScope();
invokeDeactivate(deactivateProc,
isBoundSupplier.get(),
connectionId,
setRunningProcFunc);
}
}
/**
* Create a new instance of the specified qualified class name.
*
* @param invocationResult
* The result to be sent back to the remote side.
* @param connectionId
* The appserver connection id.
* @param agentId
* The agent id.
* @param sessionId
* The appserver session id.
* @param activateProc
* The session activate procedure.
* @param deactivateProc
* The session deactivate procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param bindPersistentFunc
* The function binding the persistent procedure.
* @param unbindPersistentFunc
* The function unbinding the persistent procedure.
* @param addProcedureFunc
* The function adding the persistent procedure.
* @param disconnectFunc
* The function disconnecting the client.
* @param name
* The legacy qualified class name for the legacy class.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The constructor's arguments.
*/
public static void newInstance(AppServerInvocationResult invocationResult,
String connectionId,
Integer agentId,
Integer sessionId,
String activateProc,
String deactivateProc,
Supplier<Boolean> isBoundSupplier,
BiConsumer<String, String> setRunningProcFunc,
BiConsumer<String, Integer> bindPersistentFunc,
BiConsumer<String, Integer> unbindPersistentFunc,
BiConsumer<String, handle> addProcedureFunc,
Consumer<String> disconnectFunc,
character name,
String modes,
Object[] args)
{
if (!invokeActivate(activateProc, deactivateProc, isBoundSupplier, connectionId, invocationResult,
setRunningProcFunc, disconnectFunc))
{
return;
}
setRunningProcFunc.accept(connectionId, name == null ? null : name.toStringMessage());
try
{
final handle hProc = new handle();
// add the topmost scope to the TransactionManager
TransactionManager.pushScope("startup",
TransactionManager.NO_TRANSACTION,
true,
true,
false,
false);
object<? extends BaseObject> instance = new ObjectVar<>(BaseObject.class);
// make a copy of the requester's args
Object[] requesterArgs = new Object[args.length];
System.arraycopy(args, 0, requesterArgs, 0, requesterArgs.length);
try
{
bindPersistentFunc.accept(connectionId, sessionId);
preProcessArguments(modes, args);
// this will 'pin' at least a reference, so that it will not be implicitly deleted
instance.assign(ObjectOps.newDynamicInstance(name, modes, args));
if (!instance.isUnknown())
{
hProc.assign(new ExternalProgramWrapper(instance.ref()));
}
}
catch (Throwable t)
{
unbindPersistentFunc.accept(connectionId, sessionId);
if (t instanceof StopConditionException)
{
if (invokeFailure((StopConditionException) t))
{
return;
}
}
throw t;
}
finally
{
Object[] finalArgs = postProcessArguments(modes, args, requesterArgs);
invocationResult.setArguments(finalArgs);
invocationResult.setModes(modes);
// remove the topmost scope from the TransactionManager
TransactionManager.popScope();
}
// set the agent's procedure
addProcedureFunc.accept(connectionId, hProc);
invocationResult.setProcedureId(hProc.toString());
invocationResult.setAgentId(agentId);
}
finally
{
invokeDeactivate(deactivateProc, isBoundSupplier.get(), connectionId, setRunningProcFunc);
}
}
/**
* Invoke a method in a remotely instantiated class.
*
* @param invocationResult
* The result to be sent back to the remote side.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
* @param connectionId
* The appserver connection id.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param returnValue
* Flag indicating if the return value is required.
* @param name
* The legacy 4GL name for the class method.
* @param resId
* The code of a remotely instantiated object in which to invoke the method.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The method's arguments.
*/
public static void invokeMethod(AppServerInvocationResult invocationResult,
boolean isMultiSession,
String connectionId,
Supplier<Boolean> isBoundSupplier,
BiConsumer<String, String> setRunningProcFunc,
boolean returnValue,
character name,
String resId,
String modes,
Object[] args)
{
if (!isBoundSupplier.get())
{
// TODO: error out?
return;
}
// resolve the procedure - this is actually 'extProg', validated when the agent was acquired.
handle proc = handle.fromString(resId);
String className = proc.get().getClass().getName();
String methodName = name.toStringMessage();
setRunningProcFunc.accept(connectionId, className + " # " + methodName);
object<? extends BaseObject> ref = new object<>((BaseObject) proc.get());
// add the topmost scope to the TransactionManager
TransactionManager.pushScope("startup",
TransactionManager.NO_TRANSACTION,
true,
true,
false,
false);
try
{
invokeMethodImpl(invocationResult,
isMultiSession,
name,
ref,
returnValue,
modes,
args);
}
finally
{
// remove the topmost scope from the TransactionManager
TransactionManager.popScope();
}
}
/**
* Invoke the given Java method directly.
* <p>
* All arguments are sent by references: if it was marked as an OUTPUT or INPUT-OUTPUT, any change to that
* instance will be reflected back to the caller.
*
* @param invocationResult
* The result where to save the arguments and any return value.
* @param connectionId
* The appserver connection id.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
* @param activateProc
* The session activate procedure.
* @param deactivateProc
* The session deactivate procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param disconnectFunc
* The function disconnecting the client.
* @param cfg
* The configuration with the Java method to be invoked.
*/
public static void invokeJavaMethod(AppServerInvocationResult invocationResult,
String connectionId,
boolean isMultiSession,
String activateProc,
String deactivateProc,
Supplier<Boolean> isBoundSupplier,
BiConsumer<String, String> setRunningProcFunc,
Consumer<String> disconnectFunc,
JavaInvokeConfig cfg)
{
if (!invokeActivate(activateProc, deactivateProc, isBoundSupplier, connectionId, invocationResult,
setRunningProcFunc, disconnectFunc))
{
return;
}
setRunningProcFunc.accept(connectionId, cfg.getClazz().getName() + " # " + cfg.getTarget());
boolean disconnect = false;
try
{
disconnect = ((logical) executeScoped(() -> {
boolean isQuit = false;
Object[] args = cfg.getArguments() == null ? new Object[0] : cfg.getArguments();
try
{
Method mthd = cfg.getMethod();
Class<?> clz = cfg.getClazz();
Object result = null;
Object instance = null;
if (!Modifier.isStatic(mthd.getModifiers()))
{
instance = clz.getDeclaredConstructor().newInstance();
}
result = mthd.invoke(instance, args);
invocationResult.setResult(result);
}
catch (ErrorConditionException e)
{
checkError(invocationResult, e, isMultiSession);
}
catch (QuitConditionException e)
{
invocationResult.setError(e);
isQuit = true;
}
catch (StopConditionException e)
{
if (invokeFailure(e))
{
return new logical(false);
}
throw e;
}
catch (Throwable t)
{
boolean ignore = false;
Throwable cause = t;
while (cause != null)
{
if (cause instanceof UnstoppableExitException)
{
LogicalTerminal.activateBatchMode(true);
ignore = true;
break;
}
cause = cause.getCause();
}
if (!ignore)
{
invocationResult.setError(t);
}
}
finally
{
invocationResult.setArguments(args);
}
return new logical(isQuit);
}))
.booleanValue();
}
finally
{
invokeDeactivate(deactivateProc, isBoundSupplier.get(), connectionId, setRunningProcFunc);
if (disconnect)
{
disconnectFunc.accept(connectionId);
}
}
}
/**
* Obtain a proxy for the given external procedure. This will also reserve an agent session which will
* be used for all subsequent invocation (including external procedure initialization).
*
* @param hProc
* The handle to be assigned the proxy procedure.
* @param invocationResult
* The result where to save the arguments and any return value.
* @param exports
* The export list for remote procedures the client can execute in the current session.
* @param activateProc
* The session activate procedure.
* @param deactivateProc
* The session deactivate procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param connectionId
* The appserver connection id.
* @param agentId
* The agent id.
* @param sessionId
* The appserver session id.
* @param externalProcName
* A name of a remote external procedure.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param bindPersistentFunc
* The function binding the persistent procedure.
* @param addProcedureFunc
* The function adding the persistent procedure.
* @param disconnectFunc
* The function disconnecting the client.
*/
public static void obtainProxy(handle hProc,
AppServerInvocationResult invocationResult,
character exports,
String activateProc,
String deactivateProc,
Supplier<Boolean> isBoundSupplier,
String connectionId,
Integer agentId,
Integer sessionId,
String externalProcName,
BiConsumer<String, String> setRunningProcFunc,
BiConsumer<String, Integer> bindPersistentFunc,
BiConsumer<String, handle> addProcedureFunc,
Consumer<String> disconnectFunc)
{
if (!invokeActivate(activateProc, deactivateProc, isBoundSupplier, connectionId, invocationResult,
setRunningProcFunc, disconnectFunc))
{
return;
}
setRunningProcFunc.accept(connectionId, externalProcName);
// add the topmost scope to the TransactionManager; this will be popped only when:
// a. the ext prog can't be resolved
// b. the ext prog has been initialized
TransactionManager.pushScope("startup",
TransactionManager.NO_TRANSACTION,
true,
true,
false,
false);
ExternalProgramWrapper extProg = null;
try
{
extProg = ControlFlowOps.resolveExternalProcedure(externalProcName, exports);
if (extProg != null)
{
hProc.assign(extProg);
// make it bound only if the invocation was successful
bindPersistentFunc.accept(connectionId, sessionId);
// associate the agent with this ext prog
addProcedureFunc.accept(connectionId, hProc);
}
}
finally
{
if (extProg == null)
{
// remove the topmost scope from the TransactionManager, as the ext prog could
// not be resolved
TransactionManager.popScope();
invokeDeactivate(deactivateProc, isBoundSupplier.get(), connectionId, setRunningProcFunc);
}
else
{
invocationResult.setAgentId(agentId);
}
}
}
/**
* Initialize the specified proxy procedure, by calling the <code>execute</code> method.
*
* @param invocationResult
* The result where to save the arguments and any return value.
* @param persistentProcResId
* The external procedure resource id.
* @param extProg
* The external procedure associated with the appserver / connection.
* @param connectionId
* The appserver connection id.
* @param agentId
* The agent id.
* @param sessionId
* The appserver session id.
* @param requestId
* The ID of the request.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
* @param setRunningProcFunc
* The function setting the running procedure.
* @param removeProcedureFunc
* The function removing the procedure.
* @param unbindPersistentFunc
* The function unbinding the persistent procedure.
* @param isBoundSupplier
* The supplier returning the bound state of the session.
* @param deactivateProc
* The session deactivate procedure.
*/
public static void initializeProxy(AppServerInvocationResult invocationResult,
String persistentProcResId,
ExternalProgramWrapper extProg,
String connectionId,
Integer agentId,
Integer sessionId,
int requestId,
boolean transactionDistinct,
String modes,
Object[] args,
BiConsumer<String, String> setRunningProcFunc,
BiConsumer<String, String> removeProcedureFunc,
BiConsumer<String, Integer> unbindPersistentFunc,
Supplier<Boolean> isBoundSupplier,
String deactivateProc)
{
setRunningProcFunc.accept(connectionId, extProg.getFileName().toStringMessage());
// make a copy of the requester's args
Object[] requesterArgs = new Object[args.length];
System.arraycopy(args, 0, requesterArgs, 0, requesterArgs.length);
try
{
// WARNING: do not add a scope here - obtainProxy will add it and keep it on the stack
try
{
preProcessArguments(modes, args);
ControlFlowOps.initializeExternalProcedure(extProg,
transactionDistinct,
modes,
args);
}
catch (StopConditionException e)
{
if (invokeFailure(e))
{
return;
}
throw e;
}
finally
{
Object[] finalArgs = postProcessArguments(modes, args, requesterArgs);
invocationResult.setArguments(finalArgs);
invocationResult.setModes(modes);
// remove the topmost scope from the TransactionManager. this scope was added by obtainProxy
TransactionManager.popScope();
}
if (ControlFlowOps.isReturnValueSetInRootScope())
{
invocationResult.setResult(ControlFlowOps.getReturnValue());
}
invocationResult.setProcedureId(new handle(extProg).toString());
invocationResult.setAgentId(agentId);
invocationResult.setSessionId(sessionId);
invocationResult.setRequestId(String.valueOf(requestId));
}
finally
{
if (!extProg.valid())
{
removeProcedureFunc.accept(connectionId, persistentProcResId);
unbindPersistentFunc.accept(connectionId, sessionId);
}
invokeDeactivate(deactivateProc, isBoundSupplier.get(), connectionId, setRunningProcFunc);
}
}
/**
* Handle a generic web service request. This exposes the Jetty request and response objects
* to the Agent which will process it, so it is required for the appserver to be local.
*
* @param invocationResult
* The result where to save the arguments and any return value.
* @param connectionId
* The appserver connection id.
* @param handler
* The {@link com.goldencode.p2j.oo.web.WebHandler} implementation.
* @param basepath
* The service basepath.
* @param paths
* The split paths for this handler.
* @param target
* The request original target.
* @param request
* The Jetty request.
* @param response
* The Jetty response.
* @param setRunningProcFunc
* The function setting the running procedure.
*/
public static void handleWebService(AppServerInvocationResult invocationResult,
String connectionId,
Class<? extends WebHandler> handler,
String basepath,
String[] paths,
String target,
HttpServletRequest request,
HttpServletResponse response,
BiConsumer<String, String> setRunningProcFunc)
{
object<WebHandler> ohandler = TypeFactory.object(WebHandler.class);
setRunningProcFunc.accept(connectionId, target);
// add the topmost scope to the TransactionManager
TransactionManager.pushScope("startup",
TransactionManager.NO_TRANSACTION,
true,
true,
false,
false);
try
{
try
{
WebHandler.initialize(basepath, target, paths, request, response);
ohandler.assign(ObjectOps.newInstance(handler));
if (!ohandler._isValid())
{
// TODO:
return;
}
@SuppressWarnings("unused")
integer res = ohandler.ref().handleRequest();
// TODO: non-zero means a static error page must be shown, for that error...
}
catch (Throwable t)
{
invocationResult.setError(t);
}
finally
{
if (ohandler._isValid())
{
ObjectOps.delete(ohandler);
}
WebHandler.clear();
}
}
finally
{
// remove the topmost scope from the TransactionManager
TransactionManager.popScope();
}
}
/**
* Check if the specified ERROR condition needs to be reported.
*
* @param invocationResult
* The result where to save the error.
* @param e
* The ERROR condition.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
*/
public static void checkError(AppServerInvocationResult invocationResult,
ErrorConditionException e,
boolean isMultiSession)
{
if (ControlFlowOps.isReturnValueSetInRootScope())
{
if (isMultiSession)
{
invocationResult.setError(e);
}
else
{
String msg = ControlFlowOps.getReturnValue().toStringMessage();
invocationResult.setError(new ErrorConditionException(msg));
}
return;
}
Throwable cause = e.getCause();
if (cause instanceof NumberedException)
{
// only the following codes will be sent to the remote side
// TODO: this needs to be done only if the errors are caused by problems in
// resolving the appserver call target, and not by nested RUN statements
final Set<Integer> notifyErrs = new HashSet<Integer>();
final Integer[] codes =
{ 6349, 3230, 1005, 3234, 5729, 2570, 11428, 12290, 15472, 5639, 6445, 8030,
12324 };
notifyErrs.addAll(Arrays.asList(codes));
NumberedException ne = (NumberedException) cause;
int num = ne.getNumber();
if (notifyErrs.contains(num))
{
invocationResult.setError(e);
}
}
if (invocationResult.getError() == null)
{
cause = e;
while (cause != null)
{
if (cause instanceof ConditionException)
{
invocationResult.setError(cause);
break;
}
cause = cause.getCause();
}
}
if (invocationResult.getError() == null)
{
invocationResult.setError(e);
}
}
/**
* Execute the specified task scoped to a topmost, global block.
*
* @param task
* The task to execute.
*
* @return The value as returned by the task.
*/
public static BaseDataType executeScoped(Supplier<BaseDataType> task)
{
// add the topmost scope to the TransactionManager
TransactionManager.pushScope("startup",
TransactionManager.NO_TRANSACTION,
true,
true,
false,
false);
try
{
return task.get();
}
catch (StopConditionException e)
{
// check if this can be converted to error
if (invokeFailure(e))
{
return new unknown();
}
throw e;
}
finally
{
// remove the topmost scope from the TransactionManager
TransactionManager.popScope();
}
}
/**
* Populates the CURRENT-REQUEST-INFO & CURRENT-RESPONSE-INFO.
*
* @param cfg
* The invoke configuration.
* @param isMultiSession
* Flag to indicate running in a multi-session agent appserver.
* @param agentId
* The agent id.
* @param threadId
* The thread id.
* @param sessionId
* The appserver session id.
*/
static void setOeRequestInfo(InvokeConfig cfg,
boolean isMultiSession,
Integer agentId,
Integer threadId,
Integer sessionId)
{
object<? extends OerequestInfo> oreq =
(object) executeScoped(() -> SessionUtils.currentRequestInfo());
object<? extends OerequestInfo> oresp =
(object) executeScoped(() -> SessionUtils.currentResponseInfo());
OerequestInfo req = AppserverPackageExports.convertToOeRequest(oreq);
if (req == null)
{
return;
}
OerequestInfo resp = AppserverPackageExports.convertToOeRequest(oresp);
executeScoped(() ->
{
if (cfg != null)
{
cfg.restoreRequest(req, isMultiSession ? agentId : null, sessionId, threadId);
}
else
{
req.initialize(null, null, null, null, null, agentId, sessionId, threadId, null);
}
resp.initialize(req, false);
return null;
});
}
/**
* Check if the given STOP condition is from an invoke failure.
*
* @param e
* The STOP condition.
*
* @return <code>true</code> if the cause if this STOP is a {@link NumberedException} known
* to be produced by invoke failures.
*/
private static boolean invokeFailure(StopConditionException e)
{
Throwable cause = e.getCause();
if (cause != null && cause instanceof NumberedException)
{
// this specific case treats the error raised by ControlFlowOps.invokeFailure, which will
// always set the topmost cause as a NumberedException
NumberedException ne = (NumberedException) cause;
if (ne.getNumber() == 293)
{
// throws a ControlFlowOps.invokeFailure incoming error to allow the current command
// to abort (which is a RUN of an external program) and will just 'eat' the exception
// and copy it to the RemoteInvocationResult instance; it should be safe to directly
// instantiate the error because we are outside of 4GL normal behavior; anyway, the
// idea is also 'this needs to be thrown'
// raise ERROR condition if the external procedure was not found
if (e instanceof StopConditionException)
{
throw e;
}
else
{
throw new ErrorConditionException(e);
}
}
}
return false;
}
/**
* Preprocess the arguments sent by the requester so that they are converted to instances
* compatible with the P2J runtime. This includes:
* <ul>
* <li>Creating the handles for all received resources</li>
* <li>Creating the temp-tables for the received {@link TableWrapper} instances</li>
* <li>Creating {@link memptr} instances from all {@link MemoryBuffer} instances</li>
* <li>Creating {@link object} instances from all {@link LegacyObject} instances</li>
* </ul>
*
* @param requesterModes
* The parameter modes sent by the requester.
* @param args
* The argument list sent by the requester. The elements in this array will be
* changed as needed.
*/
private static void preProcessArguments(String requesterModes, Object[] args)
{
ControlFlowOps.resetReturnValueSetInRootScope();
String modes = requesterModes;
if (modes == null || modes.length() != args.length)
{
// no modes specified or incompatible modes
return;
}
// a bogus class... for dataset parameters
ProcedureManager.setInstantingExternalProgramClass(Object.class);
for (int i = 0; i < modes.length(); i++)
{
Object arg = args[i];
boolean inputOutput = modes.charAt(i) == 'U';
boolean output = modes.charAt(i) == 'O';
boolean input = modes.charAt(i) == 'I';
if (arg instanceof TableWrapper)
{
// a result set will be sent only if the requester and the appserver are not in the
// same JVM
TableWrapper rs = (TableWrapper) arg;
arg = new TableParameter(rs,
ParameterOption.APPEND,
input || inputOutput,
output || inputOutput);
((TableParameter) arg).setParameterIndex(i + 1);
}
else if (arg instanceof DatasetWrapper)
{
DatasetWrapper ds = (DatasetWrapper) arg;
arg = new DataSetParameter(ds);
((DataSetParameter) arg).setParameterIndex(i + 1);
}
else
{
boolean isArray = arg.getClass().isArray();
Object[] data = null;
if (isArray)
{
// check if this argument is an array
Class<?> ctype = arg.getClass().getComponentType();
if (ctype == MemoryBuffer.class)
{
ctype = memptr.class;
}
else if (ctype == LegacyObject.class)
{
ctype = ObjectVar.class;
}
data = (Object[]) Array.newInstance(ctype, Array.getLength(arg));
for (int j = 0; j < data.length; j++)
{
Object jarg = Array.get(arg, j);
if (jarg instanceof MemoryBuffer)
{
jarg = new memptr(((MemoryBuffer) jarg).getValue());
}
else if (jarg instanceof LegacyObject)
{
LegacyObject ser = (LegacyObject) jarg;
jarg = new ObjectVar(ser.getType());
_BaseObject_ ref = ser.restore();
((ObjectVar) jarg).assign(ref);
}
data[j] = jarg;
}
}
else
{
data = new Object[] { arg };
}
for (int j = 0; j < data.length; j++)
{
Object darg = data[j];
if (darg instanceof RemoteResource)
{
String resid = ((RemoteResource) darg).getResourceId();
darg = (resid == null ? new handle() : handle.fromString(resid));
}
else if (darg instanceof MemoryBuffer)
{
// convert MemoryBuffer instances to memptr instances
byte[] bytes = ((MemoryBuffer) darg).getValue();
darg = bytes == null ? new memptr() : new memptr(bytes);
}
else if (darg instanceof LegacyObject)
{
LegacyObject ser = (LegacyObject) darg;
_BaseObject_ ref = ser.restore();
ObjectVar o = new ObjectVar(ser.getType());
o.assign(ref);
darg = o;
}
data[j] = darg;
}
if (isArray)
{
if (inputOutput || output)
{
BaseDataType[] extArg = (BaseDataType[]) data;
int idx = i;
if (inputOutput)
{
arg = new InputOutputExtentParameter<BaseDataType>()
{
@Override
public BaseDataType[] getVariable()
{
return extArg;
}
@Override
public void setVariable(BaseDataType[] newRef)
{
args[idx] = newRef;
}
};
}
else
{
arg = new OutputExtentParameter<BaseDataType>()
{
@Override
public BaseDataType[] getVariable()
{
return extArg;
}
@Override
public void setVariable(BaseDataType[] newRef)
{
args[idx] = newRef;
}
};
}
}
else
{
arg = data;
}
}
else
{
arg = data[0];
}
}
args[i] = arg;
}
ProcedureManager.setInstantingExternalProgramClass(null);
}
/**
* Post-process the given arguments so that all OUTPUT and INPUT-OUTPUT arguments will be sent
* back to the requester. This includes:
* <ul>
* <li>Serializing all table parameters via {@link TableWrapper} instances</li>
* <li>Sending the resource IDs for all handle parameters</li>
* <li>Converting all {@link memptr} instances to {@link MemoryBuffer} instances</li>
* <li>Converting all {@link object} instances to {@link LegacyObject} instances</li>
* </ul>
*
* @param requesterModes
* The parameter modes sent by the requester.
* @param localArgs
* The argument list after the call has been performed.
* @param requesterArgs
* The argument list sent by the requester.
*
* @return The postprocessed arguments, to be sent back to the requester.
*/
private static Object[] postProcessArguments(String requesterModes,
Object[] localArgs,
Object[] requesterArgs)
{
try
{
String modes = requesterModes;
if (modes == null || requesterArgs.length != modes.length())
{
// no modes specified, so no arguments can be returned - assume all are input
return new Object[0];
}
Object[] arguments = new Object[localArgs.length];
for (int i = 0; i < modes.length(); i++)
{
char mode = modes.charAt(i);
// in input mode, the argument is not sent back
Object localArg = (mode == 'O' || mode == 'U' ? localArgs[i] : null);
if (localArg == null)
{
// nothing to post-process if the local arg was determined to be null
arguments[i] = null;
continue;
}
Object requesterArg = requesterArgs[i];
// if output/input-output mode, send back the data.
// a table parameter is serialized to a result set only if the requester has sent a
// result set
if (localArg instanceof TableParameter && requesterArg instanceof TableWrapper)
{
// we have a DMO on the remote side, use the P2J persistence layer to get the data
TableParameter tableParam = (TableParameter) localArg;
localArg = tableParam.getResultSet();
}
else if (localArg instanceof DataSetParameter && requesterArg instanceof DatasetWrapper)
{
DataSetParameter dsParam = (DataSetParameter) localArg;
localArg = dsParam.getDatasetWrapper();
}
else
{
if (localArg instanceof AbstractExtentParameter)
{
localArg = ((AbstractExtentParameter) localArg).getVariableSafe();
}
boolean isArray = localArg.getClass().isArray();
Object[] data = null;
if (isArray)
{
// check if this argument is an array
Class<?> ctype = localArg.getClass().getComponentType();
if (ctype == memptr.class)
{
ctype = MemoryBuffer.class;
}
else if (object.class.isAssignableFrom(ctype))
{
ctype = LegacyObject.class;
}
data = (Object[]) Array.newInstance(ctype, Array.getLength(localArg));
for (int j = 0; j < data.length; j++)
{
Object jarg = Array.get(localArg, j);
if (jarg.getClass() == memptr.class)
{
jarg = new MemoryBuffer(((memptr) jarg).asByteArray());
}
else if (object.class.isAssignableFrom(jarg.getClass()))
{
jarg = new LegacyObject((object) jarg);
}
data[j] = jarg;
}
}
else
{
data = new Object[] { localArg };
}
for (int j = 0; j < data.length; j++)
{
Object dlocalArg = data[j];
if (dlocalArg instanceof handle)
{
handle h = (handle) dlocalArg;
dlocalArg = new RemoteResourceImpl(h.isUnknown() ? null : h.toStringMessage());
}
else if (dlocalArg instanceof memptr)
{
// convert memptr instances to MemoryBuffer instances
dlocalArg = new MemoryBuffer(((memptr) dlocalArg).asByteArray());
}
else if (dlocalArg instanceof object)
{
// convert memptr instances to MemoryBuffer instances
dlocalArg = AppserverPackageExports.convertToLegacyObject(dlocalArg);
}
data[j] = dlocalArg;
}
localArg = (isArray ? data : data[0]);
}
arguments[i] = localArg;
}
return arguments;
}
finally
{
for (int i = 0; i < localArgs.length; i++)
{
Object arg = localArgs[i];
if (arg instanceof memptr)
{
// release the memory for all memptr arguments
((memptr) arg).setLength(0);
}
}
}
}
/**
* Invoke an external procedure with no parameters.
*
* @param name
* The legacy 4GL name for the procedure/function.
*/
private static void invokeScoped(character name)
{
executeScoped(() ->
{
// force invocation of an external procedure
ControlFlowOps.invokeExternalProcedure(name, false, null, false, null, null);
return ControlFlowOps.getReturnValue();
});
}
}