ProxyProcedureWrapper.java
/*
** Module : ProxyProcedureWrapper.java
** Abstract : Implements the proxy procedure resource.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 CA 20130529 Created initial version.
** 002 CA 20130813 Added support for async requests.
** 003 CA 20130919 Resource cleanup must be done after it was deleted.
** 004 CA 20131220 Changes related to the refactoring done for web service implementation.
** 005 SVL 20140430 Fixed delete().
** 006 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.
** 007 AIL 20200622 Avoid deleting multiple times.
** CA 20201027 The remote side can delete the IN HANDLE used by the requester at the RUN statement.
** CA 20210304 setInitialized() was made public, so AppServerHelper can mark a proxy 'initialized' when
** it must use an existing persistent program resource ID.
** 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 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 008 GBB 20250403 Adding child connection id.
*/
/*
** 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;
/**
* This class implements the proxy procedure resource; on the requester side, this resource is
* linked with a remote procedure resource from the remote, appserver, side. If this resource gets
* deleted, the remote resource gets deleted too.
* <p>
* Otherwise, the procedure handle on the remote and the requester side are not linked. Changes
* performed to the resource on one side are not visible on the other side. Only the NAME and
* FILE-NAME attributes are in sync with the remote side, when accessed from the requester side.
*/
public class ProxyProcedureWrapper
extends ExternalProgramWrapper
implements RemoteResource,
AsyncRequestListener
{
/** The name of this proxy procedure. */
private String fileName = null;
/** The server used to obtain this proxy procedure. */
private ServerImpl server;
/** The previous proxy procedure (in the same server). */
private ProxyProcedureWrapper prev = null;
/** The next proxy procedure (in the same server). */
private ProxyProcedureWrapper next = null;
/** Count the number of active async internal-entry requests. */
private long asyncRequestCount = 0;
/** Flag indicating if the proxy procedure has been initialized on the remote side. */
private volatile boolean initialized = false;
/** The agent ID which created this procedure. */
private int agentId;
/** The child connection id. */
private String childConnectionId;
/**
* Instantiate a new proxy procedure. The underlying referent is the code for the remote
* procedure handle, on the appserver side.
*
* @param code
* The procedure handle code, as on the remote side.
* @param fileName
* The name of this external program.
* @param agentId
* The agent ID which created the remote procedure for this proxy.
*/
public ProxyProcedureWrapper(String code, character fileName, int agentId)
{
super(code);
this.fileName = fileName.getValue();
this.agentId = agentId;
}
/**
* Instantiate a new proxy procedure. The underlying referent is the code for the remote
* procedure handle, on the appserver side.
*
* @param code
* The procedure handle code, as on the remote side.
* @param fileName
* The name of this external program.
* @param agentId
* The agent ID which created the remote procedure for this proxy.
* @param childConnectionId
* The child connection id.
*/
public ProxyProcedureWrapper(String code, character fileName, int agentId, String childConnectionId)
{
this(code, fileName, agentId);
this.childConnectionId = childConnectionId;
}
/**
* Get the agent ID, which created the remote procedure for this proxy.
*
* @return The {@link #agentId}.
*/
public int getAgentId()
{
return agentId;
}
/**
* Returns {@link #childConnectionId}.
*
* @return See above
*/
public String getChildConnectionId()
{
return childConnectionId;
}
/**
* Returns a handle to the app server in which the procedure is ran.
*
* @return See above
*/
public handle getServerHandle()
{
return new handle(server);
}
/**
* Determine if this instance is valid.
*
* @return <code>true</code> if this instance is valid, <code>false</code> otherwise.
*/
public boolean valid()
{
return referent != null;
}
/**
* Returns the number of asynchronous requests.
*
* @return See above.
*/
@Override
public synchronized integer getAsyncRequestCount()
{
return new integer(asyncRequestCount);
}
/**
* Indicates if the procedure is a proxy procedure.
*
* @return always <code>true</code>.
*/
public logical isProxy()
{
return new logical(true);
}
/**
* Indicate if the procedure is a remote procedure.
*
* @return always <code>false</code>
*/
public logical isRemote()
{
return new logical(false);
}
/**
* This call is a no-op on proxy procedures, always returns the unknown.
*
* @return See above.
*/
public character internalEntries()
{
return new character();
}
/**
* This call is a no-op on proxy procedures, always returns the empty string.
*
* @param internalEntry
* The legacy procedure/function name.
*
* @return See above.
*/
public character getSignature(character internalEntry)
{
return new character("");
}
/**
* This call is a no-op on proxy procedures, always returns <code>false</code>.
*
* @param h
* A procedure handle.
*
* @return See above.
*/
public logical addSuperProcedure(handle h)
{
return new logical(false);
}
/**
* This call is a no-op on proxy procedures, always returns <code>false</code>.
*
* @param h
* A procedure handle.
*
* @return See above.
*/
public logical removeSuperProcedure(handle h)
{
return new logical(false);
}
/**
* This call is a no-op on proxy procedures, always returns the empty string.
*
* @return See above.
*/
public character superProcedures()
{
return new character("");
}
/**
* Check if this is a persistent procedure.
*
* @return always <code>true</code>, as proxy procedures are always persistent.
*/
public logical isPersistent()
{
return new logical(true);
}
/**
* Get the external program name associated with the proxy procedure.
*
* @return The external program name.
*/
public character getFileName()
{
return new character(fileName);
}
/**
* Get a handle for the next chained procedure.
*
* @return See above.
*/
public handle getNextSibling()
{
return new handle(next);
}
/**
* Get a handle for the previous procedure in the chain.
*
* @return See above.
*/
public handle getPrevSibling()
{
return new handle(prev);
}
/**
* Delete this proxy procedure (from the requester side) and the associated remote procedure on
* the appserver side.
*/
public void delete()
{
if (referent != null && asyncRequestCount > 0)
{
// can't delete if async req count > 0
final String msg = "Handle value supplied to the DELETE PROCEDURE/OBJECT statement " +
"has outstanding asynchronous requests. Procedure '%s'";
ErrorManager.recordOrThrowError(8980, String.format(msg, fileName == null ? "?" : fileName));
return;
}
if (referent != null && !server._connected())
{
final String err = "SERVER is not connected; cannot delete procedure handle or object";
ErrorManager.recordOrShowError(5456, err, false, false, false);
return;
}
// remove this from the chain
if (prev != null)
{
prev.next = next;
}
if (next != null)
{
next.prev = prev;
}
ServerImpl server = getServer();
// adjust the first/last procedures in the chain
if (this == server.firstProcedure().getResource())
{
server.setFirstProcedure(next);
}
if (this == server.lastProcedure().getResource())
{
server.setLastProcedure(prev);
}
// cleanup after it
handle.removeResource(this);
// delete the remote procedure
if (referent != null)
{
AppServerHelper appServer = (AppServerHelper) server.getServerHelper();
if (!appServer.deleteProcedure(this))
{
final String err = "Unable to find persistent procedure handle for proxy %s";
ErrorManager.displayError(5494, String.format(err, this.referent), false);
}
// remove the referent
this.referent = null;
}
}
/**
* Such procedures can't handle multiple deletes, so this call should fail.
*/
@Override
public boolean handleMultipleDeletes()
{
return false;
}
/**
* Get the ID of this remote resource.
*
* @return See above.
*/
public String getResourceId()
{
return (String) get();
}
/**
* Called when an internal-entry of this proxy procedure is invoked async.
*/
@Override
public synchronized void notifyStart()
{
asyncRequestCount++;
}
/**
* Called when the async call of an internal-entry for proxy procedure has finished.
*/
@Override
public synchronized void notifyFinish()
{
asyncRequestCount--;
}
/**
* Mark this proxy procedure as initialized on the remote side (its 'execute' method was invoked).
*
* @param initialized
* The initialized state.
*/
public void setInitialized(boolean initialized)
{
this.initialized = initialized;
synchronized (this)
{
notifyAll();
}
}
/**
* Calculate the hash, considering the {@link #agentId}.
*
* @return Hash code value for this object instance.
*/
@Override
public int hashCode()
{
int hash = super.hashCode();
return 37 * hash + agentId;
}
/**
* Determines if this instance and the passed-in instance are equivalent.
*
* @param obj
* The instance to compare against.
*
* @return <code>true</code> if the objects are the same.
*/
@Override
public boolean equals(Object obj)
{
return super.equals(obj) && agentId == ((ProxyProcedureWrapper) obj).agentId;
}
/**
* Check the {@link #initialized} state.
*
* @return See above.
*/
boolean isInitialized()
{
return initialized;
}
/**
* Wait for this proxy procedure to be initialized (its 'execute' method invoked) on the remote side.
*/
void waitForInitialize()
{
while (!initialized)
{
synchronized (this)
{
if (initialized)
{
break;
}
try
{
wait();
}
catch (InterruptedException e)
{
break;
}
}
}
}
/**
* Add the passed <code>proxyProcedure</code> as the previous one in the chain.
*
* @param proxyProcedure
* The proxy procedure to add to the chain.
*/
void setPrevSibling(ProxyProcedureWrapper proxyProcedure)
{
this.prev = proxyProcedure;
}
/**
* Add the passed <code>proxyProcedure</code> as the next one in the chain.
*
* @param proxyProcedure
* The proxy procedure to add to the chain.
*/
void setNextSibling(ProxyProcedureWrapper proxyProcedure)
{
this.next = proxyProcedure;
}
/**
* Set the server for this proxy procedure.
*
* @param server
* A the {@link ServerImpl} instance.
*/
void setServer(ServerImpl server)
{
this.server = server;
}
/**
* Get the {@link #server} instance for this proxy procedure.
*
* @return See above.
*/
ServerImpl getServer()
{
return this.server;
}
}