AppServerHelper.java
/*
** Module : AppServerHelper.java
** Abstract : A helper class to ease appserver connection and remote calls.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20130529 Created initial version.
** 002 CA 20130813 Track the mode of the connection (session-free or session-managed). Implement
** async calls.
** 003 CA 20140110 Added access to the table metadata received from the remote side.
** 004 CA 20140114 Changes required after extending the ServerHelper class.
** 005 CA 20140509 Before sending any command to the remote appserver, check if the connection
** was established properly; if not, throw an IllegalStateException.
** 006 SVL 20140414 Do not send input data set for output parameters.
** 007 SVL 20140611 Fixed TableWrapper processing in postProcessResult.
** 008 ECF 20140624 Removed client-side Hibernate dependency.
** 009 SVL 20140709 Send table name for input tables. Create new table on the called side if the
** destination table is not specified.
** 010 CA 20140813 Fixed an index problem in TableResultSet.nextRow (anon class).
** 011 CA 20150416 Added isConnected which, beside checking if the appserver is connected, checks
** if the P2J session is still running.
** Added javadocs related to the unchecked exceptions thrown by the exposed APIs.
** 012 CA 20160404 Enhanced the appserver invocations, to allow implicit or explicit termination
** of requests being executing on a certain connection.
** 013 ECF 20170817 Added comment.
** 014 CA 20181112 Fixed a case of IndexOutOfBounds, when the call failed but the number of
** arguments differs (at the requester and remote side).
** 015 CA 20190118 disconnect() must be aware if the FWD session is gone.
** 016 CA 20190612 Added APIs to create remote legacy objects and invoke methods on them.
** 017 CA 20190628 Fixed issues when executing in-server appserver calls (disconnections and
** passing TABLE parameters).
** CA 20190703 Added WebHandler service support.
** 018 CA 20190714 Added DATASET support.
** CA 20190731 Allow remote side to send and receive data when the parameter is defined as a
** TABLE-HANDLE, thus the TABLE needs to be created at runtime.
** CA 20190812 Fixed more cases of FWD appserver requests in the same server, related to
** transporting DATASET and TABLE parameters.
** Added a way to debug the request and response arguments.
** 019 CA 20191119 Added support for before-table.
** 020 CA 20200427 Added OERequestInfo support. Fixed memptr transfer.
** CA 20200514 Fixed error handling for handleWebService.
** CA 20200528 Fixes for extent arguments.
** 021 CA 20200827 Reworked asynchronous invocations to perform all context-local work on the Conversation
** thread, and let only the actual invocation be performed in an AssociatedThread.
** 022 RFB 20200627 Fix a NPE that occurs downstream by having postProcessResult bypass a null tablename.
** CA 20200914 Fixed a problem with single-run procedures (was checking DeferredProgram instead of
** DeferredProgramWrapper).
** CA 20201027 The remote side can delete the IN HANDLE used by the requester at the RUN statement.
** OM 20210127 Replaced TableMapper.getLegacyName() triple map lookup with direct access to local
** dmoMeta.legacyTable.
** SVL 20210202 Assign return value to a CONNECT invocation result only if the value was set during this
** call.
** CA 20210304 Fixed an issue with INPUT-OUTPUT DATASET arguments.
** SVL 20210517 In preProcessParameters check if the source table is valid.
** SVL 20210517 Update to the previous fix: set TableWrapper.tableHandle flag.
** SVL 20210614 Remote OUTPUT TABLE/DATASET-HANDLE parameters are treated like INPUT-OUTPUT parameters
** with no input data rows, which allows to access table structure if non-unknown
** table/dataset handle has been passed from the calling side.
** SVL 20210616 Fixed copying rows in INPUT direction (regression caused by the previous entry).
** SVL 20210701 Fixed abend for remote DataSetContainer/DatasetWrapper parameters. Clear row data for
** remote DataSetContainer/DatasetWrapper/TableResultSet OUTPUT parameters.
** CA 20211112 Refactored to allow transfer of the DATASET or TABLE via XML, for SOAP calls.
** CA 20211214 For State-free appservers, the agents can be bound to remote persistent procedures; this
** binding must be made using the agent's ID, as a client can invoke multiple remote
** persistent procedures, and using the procedure's ID for this binding can lead to
** collisions, as the pair (connection ID, procedure ID) is not guaranteed to be unique for
** a connection.
** CA 20220110 Refactored the temp-table's row-meta state to use constants for the indexes in the array
** used for serialization.
** CA 20220208 For calls originating from outside the FWD server, do not serialize the legacy OO errors.
** Added support for legacy OO errors thrown from a remote appserver call, via RETURN ERROR.
** CA 20220329 Added support for REST services written directly in Java.
** CA 20220405 Added authentication and authorization for web requests. When this is enabled, the target
** API call will be executed under the authenticated FWD context, and not the agent's context.
** TJD 20220504 Java 11 compatibility related minor changes
** CA 20220930 InvokeConfig.target is now a String, not a character BDT.
** TJD 20220504 Java 11 compatibility related minor changes
** CA 20220428 Fixed memptr, extent, table arguments when the request is remote.
** CA 20220602 Added basic support for transport of object instances over appserver call.
** CA 20220912 If a dataset from the call has no buffers added to it, set it to unknown.
** IAS 20221029 Fixed TableParameter processing.
** 023 HC 20230116 Replaced some handle usages with the actual wrapped resources for
** performance.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 024 CA 20230321 Fixed a problem in 'connect' - can't call 'disableLegacyErrors' if the connection failed.
** 025 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 026 CA 20230607 Do not serialize requester's handle to the remote side (in JVM call will share the
** instance with the remote side).
** 027 CA 20230712 Added 'invokeWithArgs', an API which can send any number of arguments to a remote program
** acting as a controller, which can prepare and execute the real target.
** 028 CA 20231020 Only INPUT or INPUT-OUTPUT TableParameter for remote calls will populate something about
** the table.
** Fixed a regression in CA/20230607 - the handle for persistent procedure needs to be set
** again at InvokeConfig.
** 029 CA 20240327 Fixed memptr and object serialization for networked appserver calls.
** 030 CA 20240426 Track the received dataset container holds a valid dataset.
** 031 GBB 20240513 connectLegacyMode to lookupRemoteAppServer only once.
** 032 GBB 20240709 Hard-coded config names replaced by ConfigItem constants.
** 033 AP 20240802 Improved support for objects being sent as parameters to remote procedures
** inside the same JVM.
** 034 CA 20240910 Fixed appserver disconnect on QUIT from remote call.
** 035 GBB 20250403 Support connect with a service type param. Support child connection id for MSA.
** Throw excpliticly an error when the connection has timed out (14810).
** 036 CA 20250324 Improvements for remote FWD OpenClient connections: ensure that the remote side uses a
** single connection to the FWD server, and also use a client-side pool of worker threads to
** send the requests (the pool size is in sync with the maximum agents available to process
** requests).
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import java.lang.reflect.Array;
import java.net.*;
import java.sql.*;
import java.util.concurrent.atomic.*;
import javax.servlet.http.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.main.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.hql.*;
import com.goldencode.p2j.util.logging.*;
/**
* Provides a collection of APIs for connecting to the appserver and performing remote calls.
* <p>
* When the {@link #connect} or {@link #connectLegacyMode} is called, an instance of this class is
* returned which contains the connection ID and can be used to execute remote calls, via the
* determined {@link #appServer} network server.
* <p>
* When establishing connections from Java code, use the {@link #connect}, {@link #invoke} and
* {@link #invokePersistent} APIs to establish a connection and send requests to the remote side.
* The {@link #getConnectError()}, {@link #getConnectResult()} and {@link #getConnectionID()} APIs
* provide information about the state of the last CONNECT request; note that a connection ID will
* be sent back only if the connection was successful.
* <p>
* This connection established via instances of this class is not thread-safe - the client can either pool
* the connections and check them out as needed, or otherwise ensure that the connection is used on only one
* thread (this includes the {@link #connect}, invocations and {@link #disconnect} calls.
*/
public class AppServerHelper
extends ServerHelper
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(AppServerHelper.class);
/** Flag indicating if the request/response are to be dumped to files. */
private static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("DUMP_APPSERVER_DATA"));
/** A counter to ensure the debugged request/response file names are unique. */
private static final AtomicLong COUNTER = (DEBUG ? new AtomicLong(0) : null);
/**
* A local appserver instance, used when the P2J server running the appserver is the same as
* the P2J server from where the connection request originates.
*/
private static AppServerEntry localAppServer = null;
/** The id of this appserver connection. */
private final String connectionID;
/** Flag indicating this helper has disconnected. */
private boolean disconnected = false;
/** The name of the connected appserver. */
private final String appServerName;
/**
* Flag indicating if this connection is in session free mode (if true) or in session-managed
* mode (if false).
*/
private final boolean sessionFree;
/**
* A network server to the P2J server running this appserver. Each call via this server
* (except the connect-related calls) will have the connection ID as the first parameter.
*/
private final AppServerEntry appServer;
/** The result returned by this CONNECT call. */
private String connectResult = null;
/** The error encountered during the CONNECT call. */
private NumberedException connectError = null;
/**
* The P2J session used to communicate with the remote side. May be <code>null</code> if the
* appserver is in the same JVM as the requester. */
private Session session = null;
/** Flag indicating the appserver is running in the same JVM as the requester. */
private boolean local = false;
/**
* The result from the last remote invocation. This is used only for {@link #connect}, to
* restore the {@link OerequestInfo} response.
*/
private AppServerInvocationResult lastResult = null;
/** Flag indicating the legacy errors are disabled. */
private boolean disabledLegacyErrors = false;
/** Flag indicating if this instance is shared by multiple connections/threads. */
private boolean shared = false;
/**
* Initialize this helper when the connection was not established.
*/
private AppServerHelper()
{
this(null, null, null, null, false);
}
/**
* Instantiate a new helper to communicate with the specified appServer instance.
*
* @param appServerName
* The name of the connected appserver.
* @param connectionID
* The ID for this appServer connection.
* @param session
* The P2J session used to communicate with the remote side. May be <code>null</code>
* if the appServer is on the same P2J Server as the requester.
* @param appServer
* The {@link AppServerEntry} network server instance.
*/
private AppServerHelper(String appServerName,
String connectionID,
Session session,
AppServerEntry appServer,
boolean sessionFree)
{
this.appServerName = appServerName;
this.connectionID = connectionID;
this.session = session;
this.appServer = appServer;
this.sessionFree = sessionFree;
}
/**
* Authenticate with the given P2J server and establish an appserver connection. If the
* address is the address of this running P2J server, then a local proxy is resolved and no
* P2J connection is established.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The P2J account to authenticate on the remote side.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree)
{
return connect(config, account, null, null, appServerName, sessionFree, null, null, null);
}
/**
* Authenticate with the given P2J server and establish an appserver connection. If the
* address is the address of this running P2J server, then a local proxy is resolved and no
* P2J connection is established.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The P2J account to authenticate on the remote side.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
* @param webServiceType
* The type of web service the worker is used for.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree,
String webServiceType)
{
return connect(config, account, null, null, appServerName, sessionFree, null, null, null,
null, webServiceType);
}
/**
* Authenticate with the given P2J server and establish an appserver connection. If the
* address is the address of this running P2J server, then a local proxy is resolved and no
* P2J connection is established.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The P2J account to authenticate on the remote side.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
* @param user
* The username passed to the appserver's connect procedure.
* @param pwd
* The password passed to the appserver's connect procedure.
* @param serverInfo
* The info passed to the appserver's connect procedure.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connect(BootstrapConfig config,
String account,
String appServerName,
boolean sessionFree,
String user,
String pwd,
String serverInfo)
throws NumberedException
{
return connect(config, account, null, null,
appServerName, sessionFree, user, pwd, serverInfo);
}
/**
* Authenticate with the given P2J server and establish an appserver connection. If the
* address is the address of this running P2J server, then a local proxy is resolved and no
* P2J connection is established.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The P2J account to authenticate on the remote side.
* @param listener
* The session listener, used when a virtual or direct session is established.
* @param handler
* The interrupt handler, used when a direct session is established.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connect(BootstrapConfig config,
String account,
SessionListener listener,
InterruptHandler handler,
String appServerName,
boolean sessionFree)
{
return connect(config, account, listener, handler, appServerName, sessionFree,
null, null, null);
}
/**
* Authenticate with the given FWD server and establish an appserver connection. If the
* address is the address of this running FWD server, then a local proxy is resolved and no
* FWD connection is established.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The FWD account to authenticate on the remote side.
* @param listener
* The session listener, used when a virtual or direct session is established.
* @param handler
* The interrupt handler, used when a direct session is established.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
* @param user
* The username passed to the appserver's connect procedure.
* @param pwd
* The password passed to the appserver's connect procedure.
* @param serverInfo
* The info passed to the appserver's connect procedure.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connect(BootstrapConfig config,
String account,
SessionListener listener,
InterruptHandler handler,
String appServerName,
boolean sessionFree,
String user,
String pwd,
String serverInfo)
{
return connect(config,
account,
listener,
handler,
appServerName,
sessionFree,
user,
pwd,
serverInfo,
null);
}
/**
* Authenticate with the given P2J server and establish an appserver connection. If the
* address is the address of this running P2J server, then a local proxy is resolved and no
* P2J connection is established.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The P2J account to authenticate on the remote side.
* @param listener
* The session listener, used when a virtual or direct session is established.
* @param handler
* The interrupt handler, used when a direct session is established.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
* @param user
* The username passed to the appserver's connect procedure.
* @param pwd
* The password passed to the appserver's connect procedure.
* @param serverInfo
* The info passed to the appserver's connect procedure.
* @param requestInfo
* The remote {@link com.goldencode.p2j.oo.lang.OerequestInfo} details, as set by
* {@link com.goldencode.p2j.oo.lang.OerequestInfo#toArray()}.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connect(BootstrapConfig config,
String account,
SessionListener listener,
InterruptHandler handler,
String appServerName,
boolean sessionFree,
String user,
String pwd,
String serverInfo,
Object[] requestInfo)
{
return connect(config, account, listener, handler, appServerName, sessionFree, user, pwd, serverInfo,
requestInfo, null);
}
/**
* Connect to the FWD server, as specified in the given configuration.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The FWD account to authenticate on the remote side.
* @param listener
* The session listener, used when a virtual or direct session is established.
* @param handler
* The interrupt handler, used when a direct session is established.
*
* @return A record with the FWD server connection details.
*/
public static RemoteServerInfo connectToServer(BootstrapConfig config,
String account,
SessionListener listener,
InterruptHandler handler)
{
int port = -1;
boolean isSecure = config.getBoolean(ConfigItem.SECURE, false);
if (isSecure)
{
port = config.getInt(ConfigItem.SERVER_SECURE_PORT, -1);
}
else
{
port = config.getInt(ConfigItem.SERVER_PORT, -1);
if (port == -1)
{
port = config.getInt(ConfigItem.SERVER_INSECURE_PORT, -1);
}
}
String host = config.getString(ConfigItem.SERVER_HOST, "localhost");
InetSocketAddress remoteAddr = new InetSocketAddress(host, port);
// when connecting, ignore all SSL-related options from the 4GL code, as they are
// irrelevant
Session session = null;
SessionManager sessMgr = SessionManager.get();
boolean local = false;
if (sessMgr.isLeaf())
{
// this is a call originating from outside P2J code
try
{
// establish a direct connection
session = sessMgr.connectDirect(config, handler, listener);
}
catch (Exception e)
{
// the connection could not be established
return new RemoteServerInfo(new NumberedException("Connection refused by Application Server",
5467, e));
}
}
else
{
local = sessMgr.isLocalNode(remoteAddr);
if (!local)
{
try
{
// establish a virtual connection
session = sessMgr.connectVirtual(remoteAddr, null, listener, account);
}
catch (Exception e)
{
// the connection could not be established
return new RemoteServerInfo(new NumberedException("Connection refused by Application Server",
5467, e));
}
}
}
AppServerEntry appServer = null;
if (session != null)
{
appServer = (AppServerEntry) RemoteObject.obtainNetworkInstance(AppServerEntry.class, session);
}
else
{
appServer = getLocalAppServer();
}
return new RemoteServerInfo(appServer, session, local);
}
/**
* Authenticate with the given FWD server and establish an appserver connection. If the
* address is the address of this running FWD server, then a local proxy is resolved and no
* FWD connection is established.
*
* @param server
* The FWD server details.
* @param shared
* Flag indicating if the FWD server connection will be shared by separate clients connecting to
* an appserver.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
* @param user
* The username passed to the appserver's connect procedure.
* @param pwd
* The password passed to the appserver's connect procedure.
* @param serverInfo
* The info passed to the appserver's connect procedure.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connectToServer(RemoteServerInfo server,
boolean shared,
String appServerName,
boolean sessionFree,
String user,
String pwd,
String serverInfo)
{
return connectToServer(server, shared, appServerName, sessionFree, user, pwd, serverInfo, null, null);
}
/**
* Authenticate with the given FWD server and establish an appserver connection. If the
* address is the address of this running FWD server, then a local proxy is resolved and no
* FWD connection is established.
*
* @param server
* The FWD server details.
* @param shared
* Flag indicating if the FWD server connection will be shared by separate clients connecting to
* an appserver.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
* @param user
* The username passed to the appserver's connect procedure.
* @param pwd
* The password passed to the appserver's connect procedure.
* @param serverInfo
* The info passed to the appserver's connect procedure.
* @param requestInfo
* The remote {@link com.goldencode.p2j.oo.lang.OerequestInfo} details, as set by
* {@link com.goldencode.p2j.oo.lang.OerequestInfo#toArray()}.
* @param webServiceType
* The type of web service the worker is used for.
*
* @return The helper with the connection result (which was established or not).
*/
private static AppServerHelper connectToServer(RemoteServerInfo server,
boolean shared,
String appServerName,
boolean sessionFree,
String user,
String pwd,
String serverInfo,
Object[] requestInfo,
String webServiceType)
{
// the P2J account needs to be sent, as in local-proxy mode, a new context/authentication
// is not performed, and all appserver requests would be sent from the current account.
AppServerInvocationResult result = server.appServer.connect(appServerName,
sessionFree,
user,
pwd,
serverInfo,
requestInfo,
webServiceType);
String connectionID = result.getConnectionID();
AppServerHelper helper = new AppServerHelper(appServerName,
connectionID,
server.session,
server.appServer,
sessionFree);
helper.shared = shared;
if (server.session != null && connectionID != null)
{
// we are not in local appserver mode, disable legacy errors
helper.disableLegacyErrors();
}
helper.local = server.local;
helper.lastResult = result;
if (result.getResult() != null)
{
helper.setConnectResult(new character((BaseDataType) result.getResult()));
}
if (result.getError() != null || connectionID == null)
{
// the connection could not be established
helper.setConnectError(new NumberedException("Connection refused by Application Server",
5467, result.getError()));
}
return helper;
}
/**
* Authenticate with the given P2J server and establish an appserver connection. If the
* address is the address of this running P2J server, then a local proxy is resolved and no
* P2J connection is established.
*
* @param config
* The configuration for the P2J server connection.
* @param account
* The P2J account to authenticate on the remote side.
* @param listener
* The session listener, used when a virtual or direct session is established.
* @param handler
* The interrupt handler, used when a direct session is established.
* @param appServerName
* The name of the appserver to which the connection will be established.
* @param sessionFree
* <code>true</code> to indicate a session-free operating mode, <code>false</code>
* to indicate a session-managed operating mode.
* @param user
* The username passed to the appserver's connect procedure.
* @param pwd
* The password passed to the appserver's connect procedure.
* @param serverInfo
* The info passed to the appserver's connect procedure.
* @param requestInfo
* The remote {@link com.goldencode.p2j.oo.lang.OerequestInfo} details, as set by
* {@link com.goldencode.p2j.oo.lang.OerequestInfo#toArray()}.
* @param webServiceType
* The type of web service the worker is used for.
*
* @return The helper with the connection result (which was established or not).
*/
public static AppServerHelper connect(BootstrapConfig config,
String account,
SessionListener listener,
InterruptHandler handler,
String appServerName,
boolean sessionFree,
String user,
String pwd,
String serverInfo,
Object[] requestInfo,
String webServiceType)
{
RemoteServerInfo server = connectToServer(config, account, listener, handler);
if (server.connectError != null)
{
AppServerHelper helper = new AppServerHelper();
helper.setConnectError(server.connectError);
return helper;
}
return connectToServer(server, false, appServerName, sessionFree, user, pwd, serverInfo, requestInfo,
webServiceType);
}
/**
* This code is intended for legacy compatibility, and is used to connect to an appserver, when
* a CONNECT statement is encountered.
* <p>
* This will use the known appservers configured in the directory to identify the P2J server
* and account, on which the appserver targeted by the CONNECT statement is running.
*
* @param appService
* The value of the "-AppService" parameter.
* @param host
* The value of the "-H" parameter.
* @param serviceName
* The service name specified by the "-S" parameter.
* @param port
* The port specified by the "-S" parameter.
* @param sessionModel
* The session mode: session-free or session-managed.
* @param user
* The username passed to the appserver's connect procedure.
* @param pwd
* The password passed to the appserver's connect procedure.
* @param info
* The info passed to the appserver's connect procedure.
* @param request
* The remote {@link OerequestInfo} details, as set by
* {@link OerequestInfo#toArray()}.
*
* @return The helper with the connection result (which was established or not).
*/
static AppServerHelper connectLegacyMode(String appService,
String host,
String serviceName,
int port,
boolean directConnect,
String sessionModel,
character user,
character pwd,
character info,
Object[] request)
throws NumberedException
{
boolean sessionFree = AppServerSessionModel.SESSION_FREE.toString().equalsIgnoreCase(sessionModel);
AppServerManager.RemoteAppServer remoteAppServer = AppServerManager.lookupRemoteAppServer(appService,
host,
serviceName,
port,
directConnect,
sessionModel);
if (remoteAppServer == null)
{
return null;
}
InetSocketAddress address = new InetSocketAddress(remoteAppServer.p2jHost, remoteAppServer.p2jPort);
if (address.getAddress() == null)
{
throw new NumberedException(String.format("Connect failure for host %s port %s transport TCP",
host,
serviceName == null ? Integer.toString(port) : serviceName),
9407);
}
String sUser = (user == null || user.isUnknown() ? null : user.toStringMessage());
String sPwd = (pwd == null || pwd.isUnknown() ? null : pwd.toStringMessage());
String sInfo = (pwd == null || info.isUnknown() ? null : info.toStringMessage());
BootstrapConfig config = null;
try
{
config = new BootstrapConfig();
config.setConfigItem(ConfigItem.SECURE, "true");
config.setConfigItem(ConfigItem.SERVER_HOST, address.getHostName());
config.setConfigItem(ConfigItem.SERVER_SECURE_PORT, Integer.toString(address.getPort()));
}
catch (ConfigurationException e)
{
// this should never happen, but send a STOP in case it does
throw new StopConditionException(e);
}
AppServerHelper helper = connect(config, remoteAppServer.p2jAccount, null, null,
remoteAppServer.name, sessionFree, sUser, sPwd, sInfo);
// set the value returned by the CONNECT call.
character result = helper.getConnectResult();
if (result != null)
{
ControlFlowOps.setReturnValue(result);
}
if (helper.getConnectError() != null)
{
// if there was an error, trhow it here.
throw helper.getConnectError();
}
return helper;
}
/**
* Get the appserver export local to the P2J server. If not already created, obtain a local
* instance by calling {@link RemoteObject#obtainLocalInstance}.
*
* @return The local appserver instance.
*/
private static synchronized AppServerEntry getLocalAppServer()
{
if (localAppServer == null)
{
localAppServer = (AppServerEntry) RemoteObject.obtainLocalInstance(AppServerEntry.class,
true);
}
return localAppServer;
}
/**
* Disable legacy OO errors thrown on this connection.
*/
public void disableLegacyErrors()
{
checkConnected();
disabledLegacyErrors = true;
appServer.disableLegacyErrors(connectionID);
}
/**
* Get the sub-type of this server.
*
* @return Always the <code>"APPSERVER"</code> string.
*/
@Override
public String getSubType()
{
return "APPSERVER";
}
/**
* Send a STOP condition to the specified async request.
*
* @param requestId
* The request where to send a STOP condition.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
@Override
public void sendStop(int requestId)
{
checkConnected();
appServer.sendStop(connectionID, requestId);
}
/**
* Send a STOP condition to the request currently being executed on this connection.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
@Override
public void sendStop()
{
checkConnected();
appServer.sendStop(connectionID);
}
/**
* Check if this connection is in session-free mode or in session-managed mode.
*
* @return The state of the {@link #sessionFree} flag.
*/
@Override
public boolean isSessionFree()
{
return sessionFree;
}
/**
* Get the ID of this appserver connection.
*
* @return See above.
*/
@Override
public String getConnectionID()
{
return connectionID;
}
/**
* Check if this is a web service.
*
* @return Always <code>false</code>.
*/
@Override
public boolean isWebService()
{
return false;
}
/**
* Disconnect from the appserver and terminate the associated P2J session.
*
* @param disconnecting
* Flag indicating if the FWD client session is being terminated.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
@Override
public void disconnect(boolean disconnecting)
{
checkConnected();
if (session == null || session.isRunning())
{
appServer.disconnect(connectionID);
}
disconnectedImpl();
}
/**
* Explicitly disconnect the server, without invoking the remote {@link AppServerEntry#disconnect}, as
* the disconnect was from a remote QUIT condition.
*/
public void disconnectedImpl()
{
if (disconnected)
{
return;
}
if (session != null)
{
if (!shared)
{
// terminate the session
session.terminate();
}
session = null;
}
disconnected = true;
}
/**
* Check if the connection is still established.
* <p>
* This includes determining if the {@link #session} is still running and if the appserver
* connection was established.
*
* @return <code>true</code> if the server is currently connected.
*/
@Override
public boolean isConnected()
{
return !disconnected &&
(local || (session != null && session.isRunning())) &&
connectionID != null &&
connectError == null;
}
/**
* Get the name of the remote procedure, identified by the given handle.
*
* @param proc
* The handle of the remote procedure.
*
* @return The procedure's name.
*
* @throws IllegalStateException
* If the appserver is not connected or the handle is not for a proxy procedure.
*/
public character getProcedureName(handle proc)
{
if (!proc._isValid())
{
return new character();
}
if (proc.getResource() instanceof ProxyProcedureWrapper)
{
ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) proc.getResource();
return getProcedureName(proxy.getResourceId(), proxy.getAgentId());
}
else
{
throw new IllegalStateException("The handle is not for a proxy procedure!");
}
}
/**
* Get the name of the remote procedure, identified by the given proxy procedure.
*
* @param proc
* The proxy of the remote procedure.
*
* @return The procedure's name.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
public character getProcedureName(ProxyProcedureWrapper proc)
{
return getProcedureName(proc.getResourceId(), proc.getAgentId());
}
/**
* Get the name of the remote procedure, identified by the given code.
*
* @param code
* The handle code of the remote procedure.
*
* @return The procedure's name.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
public character getProcedureName(String code)
{
return getProcedureName(code, -1);
}
/**
* Get the name of the remote procedure, identified by the given code.
*
* @param code
* The handle code of the remote procedure.
* @param agentId
* The agent ID which created the procedure.
*
* @return The procedure's name.
*
* @throws IllegalStateException
* If the appserver is not connected or an agent ID was not specified for {@link #sessionFree}
* mode.
*/
public character getProcedureName(String code, int agentId)
{
checkConnected();
if (sessionFree && agentId < 0)
{
throw new IllegalStateException("The agent ID must be passed in Session-free mode!");
}
return appServer.getProcedureName(connectionID, code, agentId);
}
/**
* Delete the associated remote procedure, from the remote side.
*
* @param proc
* The remote procedure.
*
* @return <code>true</code> if the remote procedure was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected or the handle is not for a proxy procedure.
*/
public boolean deleteProcedure(handle proc)
{
if (!proc._isValid())
{
return false;
}
if (proc.getResource() instanceof ProxyProcedureWrapper)
{
ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) proc.getResource();
return deleteProcedure(proxy.getResourceId(), proxy.getAgentId(), null);
}
else
{
throw new IllegalStateException("The handle is not for a proxy procedure!");
}
}
/**
* Delete the associated remote procedure, from the remote side.
*
* @param proc
* The remote procedure.
*
* @return <code>true</code> if the remote procedure was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
public boolean deleteProcedure(ProxyProcedureWrapper proc)
{
return deleteProcedure(proc.getResourceId(), proc.getAgentId(), null);
}
/**
* Delete the associated remote procedure, from the remote side.
*
* @param procId
* The remote procedure ID.
*
* @return <code>true</code> if the remote procedure was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
public boolean deleteProcedure(String procId)
{
return deleteProcedure(procId, -1, null);
}
/**
* Delete the associated remote procedure, from the remote side.
*
* @param procId
* The remote procedure ID.
* @param agentId
* The remote agent ID.
* @param token
* When not null, it represents the token of a FWD context created for an authenticated and
* authorized web service call. The API call will be performed in that context.
*
* @return <code>true</code> if the remote procedure was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected or an agent ID was not specified for {@link #sessionFree}
* mode.
*/
public boolean deleteProcedure(String procId, int agentId, String token)
{
checkConnected();
if (sessionFree && agentId < 0)
{
throw new IllegalStateException("The agent ID must be passed in Session-free mode!");
}
return appServer.deleteProcedure(connectionID, procId, agentId, token);
}
/**
* Delete the associated remote object, from the remote side.
*
* @param proc
* The proxy for the remote procedure.
*
* @return <code>true</code> if the remote object was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected or if the handle is not for a proxy procedure.
*/
public boolean deleteObject(handle proc)
{
if (!proc._isValid())
{
return false;
}
if (proc.getResource() instanceof ProxyProcedureWrapper)
{
ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) proc.getResource();
return deleteObject(proxy.getResourceId(), proxy.getAgentId());
}
else
{
throw new IllegalStateException("The handle is not for a proxy procedure!");
}
}
/**
* Delete the associated remote object, from the remote side.
*
* @param proc
* The proxy for the remote procedure.
*
* @return <code>true</code> if the remote object was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
public boolean deleteObject(ProxyProcedureWrapper proc)
{
return deleteObject(proc.getResourceId(), proc.getAgentId());
}
/**
* Delete the associated remote object, from the remote side.
*
* @param code
* The handle code of the remote object.
*
* @return <code>true</code> if the remote object was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
public boolean deleteObject(String code)
{
return deleteObject(code, -1);
}
/**
* Delete the associated remote object, from the remote side.
*
* @param code
* The handle code of the remote object.
* @param agentId
* The remote agent ID.
*
* @return <code>true</code> if the remote object was deleted.
*
* @throws IllegalStateException
* If the appserver is not connected or an agent ID was not specified for {@link #sessionFree}
* mode.
*/
public boolean deleteObject(String code, int agentId)
{
checkConnected();
if (sessionFree && agentId < 0)
{
throw new IllegalStateException("The agent ID must be passed in Session-free mode!");
}
return appServer.deleteObject(connectionID, code, agentId);
}
/**
* Create a new instance of the specified qualified class name.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy qualified class name for the legacy class.
* @param h
* The handle where to save the proxy object.
* @param args
* The constructor's arguments.
*/
public void newInstance(long timeout,
character name,
handle h,
Object... args)
{
newInstance(timeout, name, h, (String) null, args);
}
/**
* Create a new instance of the specified qualified class name.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy qualified class name for the legacy class.
* @param h
* The handle where to save the proxy object.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The constructor's arguments.
*/
public void newInstance(long timeout,
character name,
handle h,
String modes,
Object... args)
{
checkConnected();
// preprocess the parameters and save them in a distinct array, to be sent to the remote
// side
Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);
// obtain an unique ID for this request
int reqId = appServer.uniqueId();
AppServerInvocationResult result = appServer.newInstance(connectionID,
reqId,
timeout,
name,
modes,
preprocessed);
checkResult(name.toStringMessage(), args, result);
ProxyProcedureWrapper p = new ProxyProcedureWrapper(result.getProcedureId(), name, result.getAgentId());
p.setInitialized(true);
h.assign(p);
}
/**
* Create a new instance of the specified qualified class name.
*
* @param name
* The legacy qualified class name for the legacy class.
* @param h
* The handle where to save the proxy object.
* @param args
* The constructor's arguments.
*/
public void newInstance(character name,
handle h,
Object... args)
{
newInstance(0, name, h, (String) null, args);
}
/**
* Create a new instance of the specified qualified class name.
*
* @param name
* The legacy qualified class name for the legacy class.
* @param h
* The handle where to save the proxy object.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The constructor's arguments.
*/
public void newInstance(character name,
handle h,
String modes,
Object... args)
{
newInstance(0, name, h, modes, args);
}
/**
* Invoke a method in a remotely instantiated class.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param returnValue
* Flag indicating if the return value is required.
* @param name
* The legacy 4GL name for the class method.
* @param h
* The proxy procedure in which to invoke the internal procedure or function.
* If <code>null</code>, an external procedure is invoked.
* @param args
* The method's arguments.
*
* @return The result of this method invocation, or <code>null</code> if this is a void
* method.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeMethod(long timeout,
boolean returnValue,
character name,
handle h,
Object... args)
{
return invokeMethod(timeout, returnValue, name, h, (String) null, args);
}
/**
* Invoke a method in a remotely instantiated class.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param returnValue
* Flag indicating if the return value is required.
* @param name
* The legacy 4GL name for the class method.
* @param h
* The proxy procedure in which to invoke the internal procedure or function.
* If <code>null</code>, an external procedure is invoked.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The method's arguments.
*
* @return The result of this method invocation, or <code>null</code> if this is a void
* method.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeMethod(long timeout,
boolean returnValue,
character name,
handle h,
String modes,
Object... args)
{
checkConnected();
if (!ProcedureManager.isProxy(h))
{
throw new IllegalArgumentException("The passed handle is not a proxy procedure!");
}
ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) h.getResource();
String pCode = proxy.getResourceId();
int agentId = proxy.getAgentId();
// preprocess the parameters and save them in a distinct array, to be sent to the remote
// side
Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);
// obtain an unique ID for this request
int reqId = appServer.uniqueId();
AppServerInvocationResult result = appServer.invokeMethod(connectionID, reqId, timeout,
returnValue, name, pCode, agentId,
modes, preprocessed);
checkResult(name.toStringMessage(), args, result);
return (BaseDataType) result.getResult();
}
/**
* Invoke a method in a remotely instantiated class.
*
* @param returnValue
* Flag indicating if the return value is required.
* @param name
* The legacy 4GL name for the class method.
* @param h
* The proxy procedure in which to invoke the internal procedure or function.
* If <code>null</code>, an external procedure is invoked.
* @param args
* The method's arguments.
*
* @return The result of this method invocation, or <code>null</code> if this is a void
* method.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeMethod(boolean returnValue,
character name,
handle h,
Object... args)
{
return invokeMethod(0, returnValue, name, h, (String) null, args);
}
/**
* Invoke a method in a remotely instantiated class.
*
* @param returnValue
* Flag indicating if the return value is required.
* @param name
* The legacy 4GL name for the class method.
* @param h
* The proxy procedure in which to invoke the internal procedure or function.
* If <code>null</code>, an external procedure is invoked.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The method's arguments.
*
* @return The result of this method invocation, or <code>null</code> if this is a void
* method.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeMethod(boolean returnValue,
character name,
handle h,
String modes,
Object... args)
{
return invokeMethod(0, returnValue, name, h, modes, args);
}
/**
* Invoke a remote external procedure as persistent. The validation of the legacy program name
* and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The handle where to save the proxy procedure. May be <code>null</code>.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this procedure/function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invokePersistent(character name,
handle h,
boolean transactionDistinct,
Object... args)
{
return invokePersistent(name, h, transactionDistinct, null, args);
}
/**
* Invoke a remote external procedure as persistent. The validation of the legacy program name
* and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The handle where to save the proxy procedure. May be <code>null</code>.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invokePersistent(character name,
handle h,
boolean transactionDistinct,
String modes,
Object... args)
{
return invokePersistent(0, name, h, transactionDistinct, modes, args);
}
/**
* Invoke a remote external procedure as persistent. The validation of the legacy program name
* and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The handle where to save the proxy procedure. May be <code>null</code>.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this procedure/function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invokePersistent(long timeout,
character name,
handle h,
boolean transactionDistinct,
Object... args)
{
return invokePersistent(timeout, name, h, transactionDistinct, null, args);
}
/**
* Invoke a remote external procedure as persistent. The validation of the legacy program name
* and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The handle where to save the proxy procedure. May be <code>null</code>.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invokePersistent(long timeout,
character name,
handle h,
boolean transactionDistinct,
String modes,
Object... args)
{
checkConnected();
// preprocess the parameters and save them in a distinct array, to be sent to the remote
// side
Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);
// obtain an unique ID for this request
int reqId = appServer.uniqueId();
AppServerInvocationResult result = appServer.invokePersistent(connectionID,
reqId,
timeout,
name,
transactionDistinct,
modes,
preprocessed);
checkResult(name.toStringMessage(), args, result);
if (h != null)
{
String procId = result.getProcedureId();
int agentId = result.getAgentId();
character fileName = getProcedureName(procId, agentId);
ProxyProcedureWrapper p = new ProxyProcedureWrapper(procId, fileName, agentId);
p.setInitialized(true);
h.assign(p);
}
return new character((BaseDataType) result.getResult());
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(character name,
handle h,
boolean transactionDistinct,
Object... args)
{
return invoke(name, h, true, false, false, transactionDistinct, null, args);
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(character name,
handle h,
boolean transactionDistinct,
String modes,
Object... args)
{
return invoke(name, h, true, false, false, transactionDistinct, modes, args);
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(character name,
handle h,
boolean dynamicFunction,
boolean transactionDistinct,
Object... args)
{
return invoke(name, h, true, dynamicFunction, false, transactionDistinct, null, args);
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(character name,
handle h,
boolean dynamicFunction,
boolean transactionDistinct,
String modes,
Object... args)
{
return invoke(name, h, true, dynamicFunction, false, transactionDistinct, modes, args);
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(long timeout,
character name,
handle h,
boolean transactionDistinct,
Object... args)
{
return invoke(timeout, name, h, true, false, false, transactionDistinct, null, args);
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(long timeout,
character name,
handle h,
boolean transactionDistinct,
String modes,
Object... args)
{
return invoke(timeout, name, h,
true, false, false,
transactionDistinct, modes, args);
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(long timeout,
character name,
handle h,
boolean dynamicFunction,
boolean transactionDistinct,
Object... args)
{
return invoke(timeout, name, h,
true, dynamicFunction, false,
transactionDistinct, null, args);
}
/**
* Invoke a remote remote user-defined function, using the given proxy procedure. The
* validation for the function name and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the procedure or function.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invokeFunction(long timeout,
character name,
handle h,
boolean dynamicFunction,
boolean transactionDistinct,
String modes,
Object... args)
{
return invoke(timeout, name, h,
true, dynamicFunction, false,
transactionDistinct, modes, args);
}
/**
* Invoke a remote external procedure or internal procedure. The validation of the legacy name
* and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the internal procedure.
* If <code>null</code>, an external procedure is invoked.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this procedure invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invoke(character name,
handle h,
boolean transactionDistinct,
Object... args)
{
return new character(invoke(name, h, false, false, false, transactionDistinct, null, args));
}
/**
* Invoke a remote external procedure or internal procedure. The validation of the legacy name
* and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the internal procedure.
* If <code>null</code>, an external procedure is invoked.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this procedure invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invoke(character name,
handle h,
boolean transactionDistinct,
String modes,
Object... args)
{
return new character(invoke(name, h, false, false, false, transactionDistinct, modes, args));
}
/**
* Invoke a remote external procedure, internal procedure or user-defined function. The
* validation for the program name and arguments will be done on the remote side.
*
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the internal procedure or function.
* If <code>null</code>, an external procedure is invoked.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this procedure/function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invoke(character name,
handle h,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
String modes,
Object... args)
{
return invoke(0, null, name, h,
function, dynamicFunction, superCall, transactionDistinct,
modes, args);
}
/**
* Invoke a remote external procedure or internal procedure. The validation of the legacy name
* and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the internal procedure.
* If <code>null</code>, an external procedure is invoked.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param args
* The procedure's arguments.
*
* @return The result of this procedure invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invoke(long timeout,
character name,
handle h,
boolean transactionDistinct,
Object... args)
{
return new character(invoke(timeout, name, h,
false, false, false,
transactionDistinct, null, args));
}
/**
* Invoke a remote external procedure or internal procedure. The validation of the legacy name
* and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the internal procedure.
* If <code>null</code>, an external procedure is invoked.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this procedure invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public character invoke(long timeout,
character name,
handle h,
boolean transactionDistinct,
String modes,
Object... args)
{
return new character(invoke(timeout, name, h,
false, false, false,
transactionDistinct, modes, args));
}
/**
* Invoke a remote external procedure, internal procedure or user-defined function. The
* validation for the program name and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the internal procedure or function.
* If <code>null</code>, an external procedure is invoked.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this procedure/function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
public BaseDataType invoke(long timeout,
character name,
handle h,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
String modes,
Object... args)
{
return invoke(timeout, null, name, h,
function, dynamicFunction, superCall, transactionDistinct,
modes, args);
}
/**
* Handle a generic web service request. This exposes the Jetty request and response objects
* to the Agent which will process it, so it is required for the appserver to be local.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param handler
* The {@link com.goldencode.p2j.oo.web.WebHandler} implementation.
* @param basepath
* The service basepath.
* @param paths
* The split paths for this handler.
* @param token
* When not null, it represents the token of a FWD context created for an authenticated and
* authorized web service call. The API call will be performed in that context.
* @param target
* The request original target.
* @param request
* The Jetty request.
* @param response
* The Jetty response.
*
* @return A {@link AppServerInvocationResult} instance with any error throw during the web service
* processing.
*
* @throws IllegalStateException
* If this appserver is not {@link #local}.
*/
public AppServerInvocationResult handleWebService(long timeout,
Class<?> handler,
String basepath,
String[] paths,
String token,
String target,
HttpServletRequest request,
HttpServletResponse response)
{
if (!local)
{
throw new IllegalStateException("Web services must be handled only by local appservers!");
}
int reqId = appServer.uniqueId();
return appServer.handleWebService(connectionID, reqId, timeout, handler,
basepath, paths, token, target, request, response);
}
/**
* Perform a pseudo-dynamic call where the target procedure doesn't receive the parameter as arguments at
* the Java method definition, but instead they are managed via {@link LegacyOpenClientCaller} APIs.
* <p>
* This allows the target program to act as a controller, where it can prepare the arguments, perform
* security checks, etc, before dispatching the call to the real target, which can be resolved from the
* arguments or in some other way.
*
* @param procedure
* The target external program. Must have no parameters defined.
* @param argTypes
* The argument types, one of the e.g. {@link LegacyJavaAppserverApi#integer()} values.
* @param modes
* The argument modes, one of the (I)NPUT, (O)UTPUT or INP(U)T-OUTPUT values.
* @param args
* The actual arguments.
*
* @return A {@link AppServerInvocationResult} instance with all updated OUTPUT/INPUT-OUTPUT arguments
* and any returned value.
*/
public AppServerInvocationResult invokeWithArgs(String procedure,
int[] argTypes,
String modes,
Object... args)
{
return invokeWithArgs(0, procedure, argTypes, modes, args);
}
/**
* Perform a pseudo-dynamic call where the target procedure doesn't receive the parameter as arguments at
* the Java method definition, but instead they are managed via {@link LegacyOpenClientCaller} APIs.
* <p>
* This allows the target program to act as a controller, where it can prepare the arguments, perform
* security checks, etc, before dispatching the call to the real target, which can be resolved from the
* arguments or in some other way.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param procedure
* The target external program. Must have no parameters defined.
* @param argTypes
* The argument types, one of the e.g. {@link LegacyJavaAppserverApi#integer()} values.
* @param modes
* The argument modes, one of the (I)NPUT, (O)UTPUT or INP(U)T-OUTPUT values.
* @param args
* The actual arguments.
*
* @return A {@link AppServerInvocationResult} instance with all updated OUTPUT/INPUT-OUTPUT arguments
* and any returned value.
*/
public AppServerInvocationResult invokeWithArgs(long timeout,
String procedure,
int[] argTypes,
String modes,
Object... args)
{
checkConnected();
if (argTypes.length != args.length)
{
throw new IllegalStateException("The length for argument types and the arguments must match!");
}
int modeLength = (modes == null ? 0 : modes.length());
if (modeLength != args.length)
{
throw new IllegalStateException("The length for argument modes and the arguments must match!");
}
// preprocess the parameters and save them in a distinct array, to be sent to the remote side
Object[] preprocessed = preProcessParameters(procedure, modes, args);
// obtain an unique ID for this request
int reqId = appServer.uniqueId();
AppServerInvocationResult result = appServer.invokeWithArgs(connectionID,
reqId,
timeout,
procedure,
argTypes,
modes,
preprocessed);
checkResult(procedure, args, result);
return result;
}
/**
* Get the error returned by CONNECT call.
*
* @return See above.
*/
public NumberedException getConnectError()
{
return this.connectError;
}
/**
* Get the CONNECT call result.
*
* @return See above.
*/
public character getConnectResult()
{
return new character(this.connectResult);
}
/**
* Get the name of the connected appserver.
*
* @return See above.
*/
@Override
public String getServerName()
{
return this.appServerName;
}
/**
* Get the name of the connected appserver.
*
* @return See above.
*
* @deprecated Use {@link #getServerName()} instead.
*/
@Deprecated
public String getAppServerName()
{
return this.appServerName;
}
/**
* Initialize the specified proxy procedure.
*
* @param asyncReq
* The async request which refers an uninitialized proxy procedure
* @param proxy
* The proxy procedure wrapper.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
void initializeProxy(AsyncRequestImpl asyncReq,
ProxyProcedureWrapper proxy,
boolean transactionDistinct,
String modes,
Object[] args)
{
checkConnected();
String code = proxy.getResourceId();
int agentId = proxy.getAgentId();
Object[] preprocessed = preProcessParameters(code, modes, args);
// obtain an unique ID for this request
int reqId = appServer.uniqueId();
asyncReq.setRequestId(reqId);
String connectionID = asyncReq.getConnectionId() != null ?
asyncReq.getConnectionId() :
this.connectionID;
if (!asyncReq.isAsync())
{
AppServerInvocationResult result = appServer.initializeProxy(connectionID, reqId, code, agentId,
transactionDistinct,
modes, preprocessed);
asyncReq.setResult(result);
proxy.setInitialized(true);
}
else
{
asyncReq.setAsyncAction(() ->
{
AppServerInvocationResult result = appServer.initializeProxy(connectionID, reqId, code, agentId,
transactionDistinct,
modes, preprocessed);
asyncReq.setResult(result);
proxy.setInitialized(true);
});
}
}
/**
* Obtain a proxy for the given external procedure. This will also reserve an agent which will
* be used for all subsequent invocation (including external procedure initialization).
*
* @param name
* A name of a remote external procedure.
*
* @return A result containing the details about this proxy procedure (resource ID, agent ID).
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
AppServerInvocationResult obtainProxy(String name)
{
checkConnected();
return appServer.obtainProxy(connectionID, name);
}
/**
* Invoke a remote external procedure, internal procedure or user-defined function. The
* validation for the program name and arguments will be done on the remote side.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param name
* The legacy 4GL name for the procedure/function.
* @param h
* The proxy procedure in which to invoke the internal procedure or function.
* If <code>null</code>, an external procedure is invoked.
* @param function
* <code>true</code> if this is a function call.
* @param dynamicFunction
* <code>true</code> if this is a DYNAMIC-FUNCTION call.
* @param superCall
* <code>true</code> if this is a RUN SUPER or SUPER() call.
* @param transactionDistinct
* Flag indicating if the TRANSACTION DISTINCT clause is in effect.
* @param modes
* A string representation of the modes of each parameter. May be <code>null</code>
* @param args
* The procedure's arguments.
*
* @return The result of this procedure/function invocation.
*
* @throws IllegalStateException
* If the appserver is not connected.
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
BaseDataType invoke(long timeout,
AsyncRequestImpl asyncReq,
character name,
handle h,
boolean function,
boolean dynamicFunction,
boolean superCall,
boolean transactionDistinct,
String modes,
Object... args)
{
checkConnected();
if (asyncReq != null && asyncReq._isCancelled())
{
// if the procedure is cancelled, nothing else to do
asyncReq.setResult(new AppServerInvocationResult());
return new unknown();
}
String pCode;
int agentId;
if (h != null)
{
if (!ProcedureManager.isProxy(h))
{
throw new IllegalArgumentException("The passed handle is not a proxy procedure!");
}
Object o = h.getResource();
if (o instanceof ProxyProcedureWrapper && !proxyProcedureInitialized((ProxyProcedureWrapper) o))
{
return null;
}
ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) o;
pCode = proxy.getResourceId();
agentId = proxy.getAgentId();
}
else
{
pCode = null;
agentId = -1;
}
// preprocess the parameters and save them in a distinct array, to be sent to the remote
// side
Object[] preprocessed = preProcessParameters(name.toStringMessage(), modes, args);
// obtain an unique ID for this request
int reqId = appServer.uniqueId();
if (asyncReq != null)
{
asyncReq.setRequestId(reqId);
}
if (asyncReq == null || !asyncReq.isAsync())
{
AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout,
name, pCode, agentId,
function,dynamicFunction, superCall,
transactionDistinct,
modes, preprocessed);
checkResult(name.toStringMessage(), args, result);
return (BaseDataType) result.getResult();
}
else
{
// if this is an async request, then no error messages are shown and errors are recorded
// before the EVENT-PROCEDURE is executed
asyncReq.setAsyncAction(() ->
{
AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout,
name, pCode, agentId,
function,dynamicFunction, superCall,
transactionDistinct,
modes, preprocessed);
asyncReq.setResult(result);
});
return null;
}
}
/**
* Invoke a remote external procedure, internal procedure, user-defined function or web service
* operation. The validation for the name and arguments will be specific to the established
* server connection subtype.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param asyncReq
* When not-null, an {@link AsyncRequestImpl} instance for the associated async
* request.
* @param cfg
* The invoke configuration.
*
* @return The result of this procedure/function invocation or <code>null</code> if the
* invocation could not be performed.
*/
@Override
public BaseDataType invoke(long timeout, AsyncRequestImpl asyncReq, InvokeConfig cfg)
{
checkConnected();
if (asyncReq != null && asyncReq._isCancelled())
{
// if the procedure is cancelled, nothing else to do
asyncReq.setResult(new AppServerInvocationResult());
return new unknown();
}
handle h = cfg.getInHandle();
if (h != null)
{
Object o = h.getResource();
if (!(ProcedureManager.isProxy(h) || (o instanceof DeferredProgramWrapper)))
{
throw new IllegalArgumentException("The passed handle is not a proxy procedure!");
}
if (o instanceof ProxyProcedureWrapper && !proxyProcedureInitialized((ProxyProcedureWrapper) o))
{
return null;
}
}
// preprocess the parameters and save them in a distinct array, to be sent to the remote
// side
Object[] args = cfg.getArguments() == null ? new Object[0] : cfg.getArguments();
Object[] preprocessed = preProcessParameters(cfg.getTarget(), cfg.getModes(), args);
// obtain an unique ID for this request
int reqId = appServer.uniqueId();
if (asyncReq != null)
{
asyncReq.setRequestId(reqId);
}
cfg.setArguments(preprocessed);
if (asyncReq == null || !asyncReq.isAsync())
{
// do not transfer this to the remote side, use a different instance
h = cfg.getProcedureHandle();
if (h != null)
{
cfg.setProcedureHandle(new handle());
}
AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout, cfg);
checkResult(cfg.getTarget(), args, result);
if (result.isInvalidInHandle())
{
// invalidate this handle and delete it.
ProxyProcedureWrapper proxy = (ProxyProcedureWrapper) cfg.getInHandle().getResource();
proxy.set(null);
proxy.delete();
}
if (h != null)
{
String procId = result.getProcedureId();
int agentId = result.getAgentId();
character fileName = getProcedureName(procId, agentId);
ProxyProcedureWrapper p = new ProxyProcedureWrapper(procId, fileName, agentId);
p.setInitialized(true);
h.assign(p);
cfg.setProcedureHandle(h);
}
return (BaseDataType) result.getResult();
}
else
{
// if this is an async request, then no error messages are shown and errors are recorded
// before the EVENT-PROCEDURE is executed
asyncReq.setAsyncAction(() ->
{
AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout, cfg);
asyncReq.setResult(result);
});
return null;
}
}
/**
* Invoke the given Java method directly.
* <p>
* All arguments are sent by references: if it was marked as an OUTPUT or INPUT-OUTPUT, any change to that
* instance will be reflected back to the caller.
*
* @param timeout
* The maximum allowed time for this invocation to complete. In milliseconds.
* Use <code>0</code> to disable the timeout.
* @param cfg
* The configuration with the Java method to be invoked.
*
* @return The result of this method invocation, or <code>null</code> if this is a void method.
*
* @throws JavaInvokeException
* In case any exception was thrown by the method.
*/
public Object invoke(int timeout, JavaInvokeConfig cfg)
throws JavaInvokeException
{
checkConnected();
// for Java API calls, the arguments are sent always 'as is' - there is no special preprocessing for
// them. if they are OUTPUT, they must be mutable, and postProcessResult will receive them in the
// expected state
int reqId = appServer.uniqueId();
AppServerInvocationResult result = appServer.invoke(connectionID, reqId, timeout, cfg);
if (result.getError() != null)
{
throw new JavaInvokeException("Error invoking Java service " + cfg.getTarget(), result.getError());
}
// no post-process required
return result.getResult();
}
/**
* Obtain a proxy for the given external procedure.
*
* @param name
* A name of a remote external procedure.
*
* @return A proxy to this procedure. May be invalid if the remote side can't resolve the
* procedure.
*/
ProxyProcedureWrapper obtainProxy(character name)
{
AppServerInvocationResult res = name.isUnknown() ? null : obtainProxy(name.toStringMessage());
if (res.isInvalidInHandle())
{
return new ProxyProcedureWrapper(null, name, -1);
}
else
{
ProxyProcedureWrapper proxy = new ProxyProcedureWrapper(res.getProcedureId(),
name,
res.getAgentId(),
res.getChildConnectionID());
proxy.setServer(this.server);
return proxy;
}
}
/**
* Using the details from the last appserver call, restore the SERVER:RESPONSE-INFO with the
* remote side's SESSION:CURRENT-RESPONSE-INFO, as received in the result.
*
* @param result
* The invocation result. If <code>null</code>, use {@link #lastResult}.
* @param responseInfo
* The SERVER:RESPONSE-INFO where to restore the details.
*/
void restoreResponse(AppServerInvocationResult result,
object<? extends OerequestInfo> responseInfo)
{
if (result == null)
{
result = lastResult;
lastResult = null;
}
responseInfo.assign(ObjectOps.newInstance(OerequestInfo.class));
OerequestInfo ref = responseInfo.ref();
ref.initialize(null, // the response doesn't have the procedure name...
result.getClientContextId(),
result.getRequestId(),
result.getVersionInfo(),
result.getClientType(),
result.getAgentId(),
result.getSessionId(),
result.getThreadId(),
result.getClientPrincipal());
}
/**
* Write the specified arguments to file.
*
* @param target
* The target of this remote call.
* @param request
* Flag indicating this is for a request.
* @param args
* The arguments to write.
*/
private static void writeArguments(String target, boolean request, Object[] args)
{
String fname = (request ? "request" : "response") + "-" + COUNTER.incrementAndGet() + ".txt";
try
{
BufferedWriter bw = new BufferedWriter(new FileWriter(fname));
bw.write(target);
bw.write("\n");
for (int i = 0; i < args.length; i++)
{
Object arg = args[i];
String s = arg == null ? "null"
: (arg instanceof BaseDataType
? ((BaseDataType) arg).toStringMessage()
: arg.toString());
bw.write("Argument " + i + ":\n");
bw.write(s);
bw.write('\n');
}
bw.close();
}
catch (Exception e)
{
LOG.severe("", e);
}
}
/**
* Check the parameters before a remote call. All OUTPUT or INPUT-OUTPUT parameters need to be
* a P2J data wrapper, a {@link DataModelObject DMO}, {@link TableParameter} or a
* {@link TableResultSet result set}.
* <p>
* This will not change the references in the passed argument list. Instead, it will return a
* distinct array which will need to be sent to the remote side.
*
* @param target
* The target of this remote call.
* @param modes
* The parameter modes. If <code>null</code>, no validation is performed.
* @param args
* The argument list.
*
* @return The preprocessed parameters.
*/
private Object[] preProcessParameters(String target, String modes, Object... args)
{
if (local)
{
Object[] params = new Object[args.length];
System.arraycopy(args, 0, params, 0, args.length);
for (int i = 0; i < params.length; i++)
{
Object arg = params[i];
char mode = (modes == null ? 0 : modes.charAt(i));
if (arg instanceof TableResultSet || arg instanceof TableWrapper)
{
// emulate as if it was sent over the network
TableWrapper wrapper = arg instanceof TableWrapper
? (TableWrapper) arg
: new TableWrapper((TableResultSet) arg);
// make sure the remote side receives a known class
TableWrapper param = new TableWrapper();
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
wrapper.writeExternal(out);
out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
param.readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
params[i] = param;
}
else if (arg instanceof DataSetContainer || arg instanceof DatasetWrapper)
{
// emulate as if it was sent over the network
DatasetWrapper wrapper = arg instanceof DatasetWrapper
? (DatasetWrapper) arg
: new DatasetWrapper((DataSetContainer) arg);
// make sure the remote side receives a known class
DatasetWrapper param = new DatasetWrapper();
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
wrapper.writeExternal(out);
out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
param.readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
params[i] = param;
}
else if (arg instanceof TableParameter)
{
if (modes == null)
{
throw new IllegalArgumentException("Table parameters must have their mode set!");
}
TableParameter tableParam = (TableParameter) arg;
if (tableParam.isValid())
{
Temporary tempDMO = tableParam.getTable();
boolean append = tableParam.isAppend();
boolean input = (mode == 'I' || mode == 'U');
boolean output = (mode == 'O' || mode == 'U');
int schemaMarshalLevel = tableParam.getSchemaMarshalLevel();
arg = new TableWrapper(new TempTableResultSet(tempDMO, input, output, append, input));
String tableName = ((BufferImpl) tempDMO).buffer().getDmoInfo().legacyTable;
((TableWrapper) arg).setTableName(tableName);
((TableWrapper) arg).setSchemaMarshalLevel(schemaMarshalLevel);
handle tableHandle = ((Buffer) tempDMO).tableHandle();
if (tableHandle._isValid())
{
((TableWrapper) arg).init((TempTable) tableHandle.getResource());
}
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
((TableWrapper) arg).writeExternal(out);
out.close();
arg = new TableWrapper();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
((TableWrapper) arg).readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
else
{
TableWrapper tableWrapper = new TableWrapper();
tableWrapper.setTableHandle(true); // TABLE parameter cannot be invalid
arg = tableWrapper;
}
params[i] = arg;
}
else if (arg instanceof DataSetParameter)
{
if (modes == null)
{
throw new IllegalArgumentException("Dataset parameters must have their mode set!");
}
DataSetParameter dsParam = (DataSetParameter) arg;
DataSet ds = dsParam.getDataset();
boolean append = dsParam.isAppend();
boolean input = (mode == 'I' || mode == 'U');
boolean output = (mode == 'O' || mode == 'U');
arg = new DatasetWrapper(new DataSetContainer(ds, input, output, append, input));
if (input && ds != null)
{
((DatasetWrapper) arg).setDatasetName(ds.name().toStringMessage());
}
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
((DatasetWrapper) arg).writeExternal(out);
out.close();
arg = new DatasetWrapper();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
((DatasetWrapper) arg).readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
params[i] = arg;
}
else if (params[i] instanceof memptr)
{
params[i] = new MemoryBuffer(((memptr) params[i]).asByteArray());
}
else if (params[i] instanceof object)
{
params[i] = new LegacyObject((object) params[i], true);
}
// TODO: arrays
}
if (DEBUG)
{
writeArguments(target, true, params);
}
return params;
}
// the preprocessed parameter list
Object[] params = new Object[args.length];
final String msg =
"Parameter %s in mode %s needs to be a FWD data type compatible for transport over appserver.";
for (int i = 0; i < args.length; i++)
{
Object arg = args[i];
char mode = (modes == null ? 0 : modes.charAt(i));
if (arg instanceof TableParameter)
{
if (modes == null)
{
throw new IllegalArgumentException("Table parameters must have their mode set!");
}
TableParameter tableParam = (TableParameter) arg;
Temporary tempDMO = tableParam.getTable();
boolean append = tableParam.isAppend();
boolean input = (mode == 'I' || mode == 'U');
boolean output = (mode == 'O' || mode == 'U');
// try
// {
arg = new TableWrapper(new TempTableResultSet(input ? tempDMO : null,
input,
output,
append));
if (input)
{
if (tempDMO != null && ((Buffer) tempDMO).tableHandle()._isValid())
{
((TableWrapper) arg).init((TempTable) ((BufferImpl) tempDMO).tableHandleNative());
}
String tableName = ((BufferImpl) tempDMO).buffer().getDmoInfo().legacyTable;
((TableWrapper) arg).setTableName(tableName);
}
// }
// catch (ClassNotFoundException e)
// {
// String err = String.format("Could not process temp DMO %s as a parameter!",
// tempDMO.getClass().getName());
//
// throw new IllegalArgumentException(err, e);
// }
}
else if (arg instanceof DataSetParameter)
{
if (modes == null)
{
throw new IllegalArgumentException("Dataset parameters must have their mode set!");
}
DataSetParameter dsParam = (DataSetParameter) arg;
DataSet ds = dsParam.getDataset();
boolean append = dsParam.isAppend();
boolean input = (mode == 'I' || mode == 'U');
boolean output = (mode == 'O' || mode == 'U');
arg = new DatasetWrapper(new DataSetContainer(ds, input, output, append));
if (input && ds != null)
{
((DatasetWrapper) arg).setDatasetName(ds.name().toStringMessage());
}
}
else if (arg instanceof DataSetContainer)
{
DataSetContainer ds = (DataSetContainer) arg;
if (mode == 'O')
{
ds.clearRows();
}
arg = new DatasetWrapper(ds);
}
else if (arg instanceof DatasetWrapper)
{
// just keep it, clearing rows if necessary
if (mode == 'O')
{
DataSetContainer ds = ((DatasetWrapper) arg).getResource();
if (ds != null)
{
ds.clearRows();
}
}
}
else if (arg instanceof TableWrapper)
{
// just keep it
}
else if (arg instanceof TableResultSet)
{
TableResultSet rs = (TableResultSet) arg;
if (mode == 'O')
{
rs.clearRows();
}
// make sure the remote side receives a known class
arg = new TableWrapper(rs);
}
else if (arg instanceof ResultSet)
{
// allow only input mode
if (mode != 'I')
{
throw new IllegalArgumentException(
"java.sql.ResultSet instances are allowed only for INPUT table parameters!");
}
final String err = "Could not serialize the ResultSet on position " + i + ".";
try
{
@SuppressWarnings("resource")
final ResultSet rs = (ResultSet) arg;
final ResultSetMetaData rsmd = rs.getMetaData();
final int columns = rsmd.getColumnCount();
// build a custom TableResultSet which reads the data and metadata from the
// sql-like ResultSet instance, and wraps it in a TableWrapper for transport
arg = new TableWrapper(new TableResultSet()
{
private int nextCol = 0;
@Override
public boolean hasMoreRows()
{
try
{
return rs.next();
}
catch (SQLException e)
{
throw new IllegalArgumentException(err, e);
}
}
@Override
public Object[] nextRow()
{
try
{
Object[] row = new Object[columns];
for (int i = 1; i <= columns; i++)
{
row[i - 1] = rs.getObject(i);
}
return row;
}
catch (SQLException e)
{
throw new IllegalArgumentException(err, e);
}
}
@Override
public Object[] nextRowMeta()
{
// nothing is kept here...
return new Object[TempRecord._META_PROPERTIES_LENGTH];
}
@Override
public PropertyDefinition nextProperty()
{
try
{
// TODO: extent properties?
// TODO: handle uses same SQL type as character
// TODO: rowid uses same SQL type as int64
// TODO: recid uses same SQL type as integer
int type = rsmd.getColumnType(nextCol);
return new PropertyDefinition(rsmd.getColumnName(nextCol),
DataTypeHelper.getBdtClass(type));
}
catch (SQLException e)
{
throw new IllegalArgumentException(err, e);
}
}
@Override
public boolean hasMoreProperties()
{
nextCol = nextCol + 1;
return nextCol <= columns;
}
});
}
catch (SQLException e)
{
throw new IllegalArgumentException(err, e);
}
}
else if (arg instanceof memptr)
{
arg = new MemoryBuffer(((memptr) arg).asByteArray());
}
else if (arg instanceof object)
{
arg = new LegacyObject((object) arg);
}
else
{
// check if we have an array
boolean isArray = arg.getClass().isArray();
Object[] data = null;
if (isArray)
{
// make sure we are going over each element in the array
int length = Array.getLength(arg);
data = (Object[]) Array.newInstance(arg.getClass().getComponentType(), length);
for (int j = 0; j < length; j++)
{
data[j] = Array.get(arg, j);
}
}
else
{
data = new Object[] { arg };
}
for (int j = 0; j < data.length; j++)
{
Object darg = data[j];
if (darg instanceof handle)
{
handle h = (handle) darg;
if (h.isUnknown())
{
// if the handle is unknown, send a null resource ID
darg = new RemoteResourceImpl(null);
}
else
{
WrappedResource res = h.getResource();
if (res instanceof RemoteResource)
{
darg = res;
}
else
{
// any resource can be sent to the remote side, even if the remote side
// can't resolve it properly
darg = new RemoteResourceImpl(h.toStringMessage());
}
}
}
else if (darg instanceof memptr)
{
// do not allow memptrs to be used
throw new IllegalArgumentException(
"memptr instances can not be used from Java code. Use MemoryBuffer instances instead.");
}
else if (darg instanceof object)
{
// do not allow memptrs to be used
throw new IllegalArgumentException(
"object instances can not be used from Java code. Use LegacyObject instances instead.");
}
else if ((mode == 'U' || mode == 'O') &&
!(darg instanceof BaseDataType ||
darg instanceof MemoryBuffer ||
darg instanceof LegacyObject))
{
// for output/input-output modes, darg needs to be one of the known types
String err = String.format(msg, darg, (mode == 'U' ? "INPUT-OUTPUT" : "OUTPUT"));
throw new IllegalArgumentException(err);
}
data[j] = darg;
}
arg = (isArray ? data : data[0]);
}
params[i] = arg;
}
if (DEBUG)
{
writeArguments(target, true, params);
}
return params;
}
/**
* Verify the current invocation result instance, by checking if any errors were thrown by the
* remote call.
* <p>
* Also, this receives an argument list and updates it, depending on the parameter's mode
* (OUTPUT or INPUT-OUTPUT) and if the remote side sent an updated value of this argument. The
* post-processing of the argument list is done only if no errors were received from the remote
* side.
*
* @param target
* The target of this remote call.
* @param args
* The arguments which need to be processed.
* @param result
* The result which needs to be checked for errors.
*
* @throws ConditionException
* If the appserver call ended with a ERROR, QUIT, STOP, END-ERROR condition
* @throws RuntimeException
* If the API call ended with other type of {@link Exception}, which is not a
* {@link ConditionException}.
*/
private void checkResult(String target, Object[] args, AppServerInvocationResult result)
{
// restore the response
if (server != null)
{
restoreResponse(result, server.getResponseInfo());
}
Throwable t = result.getError();
if (t != null)
{
if (t instanceof NumberedException)
{
if (!SessionManager.get().isLeaf())
{
// do this only if we are in a P2J code
NumberedException ne = (NumberedException) t;
if (server != null)
{
ErrorManager.displayError(ne.getNumber(), ne.getMessage(), false);
}
else
{
throw new ErrorConditionException(ne.getMessage());
}
}
else
{
// we are not in P2J code, let the error propagate
throw new RuntimeException(t);
}
Throwable cause = t.getCause();
if (cause instanceof StopConditionException)
{
throw (ConditionException) cause;
}
}
else if (t instanceof QuitConditionException)
{
// the Agent has disconnected because of a QUIT - you need to reconnect...
disconnectedImpl();
throw (QuitConditionException) t;
}
else if (t instanceof LegacyErrorException)
{
if (disabledLegacyErrors)
{
throw new ErrorConditionException(
"ERROR condition: The Server application has returned an error. (7243) (7211)");
}
LegacyErrorException le = (LegacyErrorException) t;
LegacyErrorException newEx = le.duplicate();
throw newEx;
}
else if (t instanceof ConditionException)
{
ConditionException conExc = (ConditionException) t;
if (conExc.getKeyCode() == 14810)
{
// TODO: maybe add more properties in the appserver result to set the other flags
ErrorManager.recordOrThrowError(new int[] { conExc.getKeyCode() },
new String[] { conExc.getMessage() },
false,
false,
false);
}
else
{
throw conExc;
}
}
else
{
throw new RuntimeException(t);
}
}
// no errors found, post-process results
postProcessResult(target, args, result);
}
/**
* On the requester side, this receives an argument list and updates it, depending on the
* parameter's mode (OUTPUT or INPUT-OUTPUT) and if the remote side sent an updated value of
* this argument.
*
* @param target
* The target of this remote call.
* @param args
* The arguments (from the requester side) which need to be processed.
* @param result
* The result which needs to be checked for errors.
*/
private void postProcessResult(String target, Object[] args, AppServerInvocationResult result)
{
Object[] remoteArgs = result.getArguments();
if (remoteArgs == null || remoteArgs.length != args.length)
{
// some error happened and the remote side did not invoke anything or no arguments used
return;
}
for (int i = 0; i < args.length; i++)
{
if (remoteArgs[i] == null)
{
// only output/input-output parameters are sent back
continue;
}
Object localArg = args[i];
Object remoteArg = remoteArgs[i];
if (remoteArg instanceof DatasetWrapper)
{
DatasetWrapper remoteResult = (DatasetWrapper) remoteArg;
if (localArg instanceof DataSetContainer || localArg instanceof DatasetWrapper)
{
if (local)
{
try
{
// emulate as if it was sent over the network...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
remoteResult.writeExternal(out);
out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
remoteResult = new DatasetWrapper();
remoteResult.readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
if (remoteResult.isAsXml())
{
((DatasetWrapper) localArg).setXmlDataset(remoteResult.getXmlDataset());
}
else
{
DataSetContainer dsParam;
if (localArg instanceof DataSetContainer)
{
dsParam = (DataSetContainer) localArg;
}
else
{
dsParam = ((DatasetWrapper) localArg).getResource();
}
dsParam.setValid(remoteResult.isValid());
dsParam.setTableDefinitions(remoteResult.getTableDefs());
dsParam.setRelationDefinitions(remoteResult.getRelationsDefs());
dsParam.setStructureName(remoteResult.getDatasetName());
}
}
else
{
DataSetParameter dsParam = (DataSetParameter) localArg;
boolean append = dsParam.isAppend();
if (local)
{
try
{
// emulate as if it was sent over the network...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
remoteResult.writeExternal(out);
out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
remoteResult = new DatasetWrapper();
remoteResult.readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
if (remoteResult.getTableDefs() == null)
{
// remote result is unknown
if (dsParam.getOriginalDataSet() != null)
{
// can't 'unknown' a static dataset
}
else
{
dsParam.getDatasetHandle().setUnknown();
}
}
else
{
DataSet ds = dsParam.getDataset();
if (ds == null)
{
dsParam.createDataSet(remoteResult);
}
else
{
// copy individual tables
int bufCnt = ds.numBuffers().intValue();
for (int k = 0; k < bufCnt; k++)
{
Buffer dstBuf = ds.getBufferByIndex(k + 1);
Temporary dstDMO = TemporaryBuffer.getDefaultBuffer(dstBuf.tableHandle());
TableWrapper src = remoteResult.getTableWrapper(k);
TemporaryBuffer.insertAllRows(src,
dstDMO,
append,
TemporaryBuffer.CopyTableMode.OUTPUT_TABLE_HANDLE_PARAM_MODE,
dsParam.getParameterIndex());
}
}
}
}
}
else if (remoteArg instanceof TableWrapper)
{
// here there are two cases:
// 1. from 4GL code, when a temp DMO is available
// 2. from Java code, when a temp DMO is not available
TableWrapper remoteResult = (TableWrapper) remoteArg;
if (localArg instanceof TableParameter)
{
if (local)
{
try
{
// emulate as if it was sent over the network...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
remoteResult.writeExternal(out);
out.close();
remoteResult = new TableWrapper();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
remoteResult.readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
// DMO on the requester side, use the P2J persistence layer to update the data
TableParameter tableParam = (TableParameter) localArg;
Temporary tempDMO = tableParam.getTable();
boolean append = tableParam.isAppend();
int tableParameterIndex = i + 1;
if (tempDMO != null)
{
TemporaryBuffer.insertAllRows(remoteResult,
tempDMO,
append,
TemporaryBuffer.CopyTableMode.OUTPUT_TABLE_HANDLE_PARAM_MODE,
tableParameterIndex);
}
else if (remoteResult.getTableName() != null)
{
TempTableBuilder builder = new TempTableBuilder();
builder.createFromTableWrapper(remoteResult, tableParameterIndex);
tableParam.getTableHandle().assign(builder);
}
}
else if (localArg instanceof TableResultSet || localArg instanceof TableWrapper)
{
if (local)
{
try
{
// emulate as if it was sent over the network...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
remoteResult.writeExternal(out);
out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
if (localArg instanceof TableWrapper)
{
remoteResult = (TableWrapper) localArg;
}
else
{
remoteResult = new TableWrapper();
}
remoteResult.readExternal(in);
in.close();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
if (remoteResult.isAsXml())
{
((TableWrapper) localArg).setXmlTable(remoteResult.getXmlTable());
}
else if (localArg instanceof TableResultSet)
{
// we don't have a DMO, let the requester have access to the result set
// the remoteArg reference must be passed to the local arg
TableResultSet localResult = (TableResultSet) localArg;
localResult.setProperties(remoteResult.getProperties());
localResult.setRows(remoteResult.getRows());
localResult.setStructureName(remoteResult.getTableName());
}
else
{
TableWrapper localResult = (TableWrapper) localArg;
localResult.setResultSet(null); // this must be cleared
localResult.setPeerDef(remoteResult.getPeerDef());
localResult.setProperties(remoteResult.getProperties());
localResult.setRows(remoteResult.getRows());
localResult.setRowsMeta(remoteResult.getRowsMeta());
localResult.setTableName(remoteResult.getTableName());
localResult.setIndexes(remoteResult.getIndexes());
localResult.setNumIndexes(remoteResult.getNumIndexes());
localResult.setXmlns(remoteResult.getXmlns());
localResult.setXmlPrefix(remoteResult.getXmlPrefix());
}
}
}
else
{
boolean isArray = localArg.getClass().isArray();
Object[] remoteData = null;
Object[] localData = null;
if (isArray)
{
// check if this argument is an array
if (remoteArg instanceof AbstractExtentParameter)
{
int len = ((AbstractExtentParameter) remoteArg).getVariable().length;
remoteData = new Object[len];
}
else
{
remoteData = new Object[Array.getLength(remoteArg)];
}
localData = new Object[Array.getLength(localArg)];
for (int j = 0; j < remoteData.length; j++)
{
if (remoteArg instanceof AbstractExtentParameter)
{
remoteData[j] = ((AbstractExtentParameter) remoteArg).getVariable()[j];
}
else
{
remoteData[j] = Array.get(remoteArg, j);
}
}
for (int j = 0; j < localData.length; j++)
{
localData[j] = Array.get(localArg, j);
}
if (localData.length == 0)
{
// must be dynamic
localData = new Object[remoteData.length];
Class<?> ctype = localArg.getClass().getComponentType();
BaseDataType bdt = BaseDataType.class.isAssignableFrom(ctype)
? BaseDataType.generateUnknown(ctype)
: null;
for (int j = 0; j < localData.length; j++)
{
if (localArg.getClass().getComponentType() == MemoryBuffer.class)
{
localData[j] = new MemoryBuffer();
}
else if (localArg.getClass().getComponentType() == LegacyObject.class)
{
localData[j] = new LegacyObject(new object(_BaseObject_.class));
}
else
{
localData[j] = bdt.duplicate();
}
}
args[i] = localData;
}
}
else
{
remoteData = new Object[] { remoteArg };
localData = new Object[] { localArg };
}
if (remoteData.length != localData.length)
{
final String msg =
"The remote side sent an array of length %d while the local argument is %d " +
"in length, for argument on position %d.";
throw new IllegalArgumentException(String.format(msg,
remoteData.length,
localData.length,
i));
}
for (int j = 0; j < remoteData.length; j++)
{
Object dremoteArg = remoteData[j];
Object dlocalArg = localData[j];
if (dremoteArg instanceof BaseDataType)
{
if (dlocalArg instanceof BaseDataType)
{
((BaseDataType) dlocalArg).assign((BaseDataType) dremoteArg);
}
else
{
// requester's arg should have been a BDT, throw an error here but this
// should never happen as preProcessParameters takes care to enforce this
// constraint
final String msg =
"Argument on index %d in OUTPUT or INPUT-OUTPUT mode must be of a P2J " +
"wrapper type.";
throw new IllegalArgumentException(String.format(msg, i));
}
}
else if (dlocalArg instanceof handle)
{
handle h = (handle) dlocalArg;
RemoteResource res = (RemoteResource) dremoteArg;
if (res.unknown())
{
h.setUnknown();
}
else
{
h.assign(res);
}
}
else if (dlocalArg instanceof MemoryBuffer)
{
MemoryBuffer remoteBuffer = (MemoryBuffer) dremoteArg;
MemoryBuffer localBuffer = (MemoryBuffer) dlocalArg;
localBuffer.setValue(remoteBuffer.getValue());
}
else if (dlocalArg instanceof memptr)
{
MemoryBuffer remoteBuffer = (MemoryBuffer) dremoteArg;
memptr localBuffer = (memptr) dlocalArg;
localBuffer.assign(remoteBuffer.getValue());
}
else if (dlocalArg instanceof LegacyObject)
{
LegacyObject remoteObject = (LegacyObject) dremoteArg;
LegacyObject localObject = (LegacyObject) dlocalArg;
localObject.assign(remoteObject);
}
else if (dlocalArg instanceof object)
{
LegacyObject remoteObject = (LegacyObject) dremoteArg;
object localObject = (object) dlocalArg;
_BaseObject_ ref = remoteObject.restore(local);
localObject.assign(ref);
}
}
}
}
if (DEBUG)
{
writeArguments(target, false, args);
}
// check if the result is a resource
if (result.getResult() instanceof RemoteResource)
{
RemoteResource res = (RemoteResource) result.getResult();
handle h = new handle();
if (res.unknown())
{
h.setUnknown();
}
else
{
h.assign(res);
}
result.setResult(h);
}
}
/**
* Set the CONNECT call result.
*
* @param result
* The result return by the latest CONNECT call.
*/
private void setConnectResult(character result)
{
this.connectResult = result.toStringMessage();
}
/**
* Set the error returned by the CONNECT call.
*
* @param error
* The error returned by the CONNECT call.
*/
private void setConnectError(NumberedException error)
{
this.connectError = error;
}
/**
* Check if the appserver is connected and if not, throw a {@link IllegalStateException}.
*
* Details about the server-side problem are found in the {@link #connectError} and in the
* {@link #connectResult} fields.
*
* @throws IllegalStateException
* If the appserver is not connected.
*/
private void checkConnected()
{
if (connectError != null)
{
String msg = "The appserver is not connected - check the connection result!";
throw new IllegalStateException(msg, connectError);
}
if (connectionID == null)
{
throw new IllegalStateException("The connection to the appserver was not established!");
}
if (!isConnected())
{
throw new IllegalStateException("The connection to the appserver was lost.");
}
}
/**
* Wait for the initialization of a proxy procedure.
*
* @param p
* The proxy procedure.
*/
private boolean proxyProcedureInitialized(ProxyProcedureWrapper p)
{
p.waitForInitialize();
if (!p.isInitialized())
{
// TODO: the proxy program has not been initialized - raise exception?
return false;
}
return true;
}
}