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