# 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;
       }
    }