/*******************************************************************************
* E.S.O. - VLT project
#
# "@(#) $Id: evhTestSTD_COMMANDS.C,v 1.87 1999/01/13 14:32:09 vltsccm Exp $" 
*
* who       when      what
* --------  --------  ----------------------------------------------
* gchiozzi  15/04/99  created
*/

/************************************************************************
*   NAME
*   waTRANSFER - Class to handle the "transfer" of a command 
*                to another process
* 
*   SYNOPSIS
*
*   
*   PARENT CLASS
*   public fndOBJECT
*   virtual public eccsERROR_CLASS
* 
*   DESCRIPTION
*   This class is used to "transfer" a command to another process, i.e.
*   to re-send a received command to another process, wait for the 
*   replies and send back the replies to the original caller, in a 
*   transparent way.
*   This is a typical behaviour for workstation coordination applications .
*   Instances of the waTRANSFER class are created using the 
*   TransferCommand() static factory method.
*   When the method is called, an object is created and the command
*   is transferred to the defined process.
*   When a last reply (normal or error reply or timeout) is received,
*   the object deletes itself.
*
*   PUBLIC METHODS
*   static ccsCOMPL_STAT TransferCommand(const ccsENVNAME   destenv, 
* 			                 const ccsPROCNAME  destproc,
*			                 const msgMESSAGE  &msg,
*			                 const eccsTIMEVAL  interval);
*     This static factory method creates an instance of the class
*     and sends the to the given process a comand identical to the 
*     one in the passed msgMESSAGE.
*     The interval parameter is the timeout for the command to be 
*     completed.
*     It is the only way to create instances of the class.
*     The objects destroy themself when handling is complete.
*
*   virtual ~waTRANSFER();
*     Destructor
*
*   PROTECTED METHODS
*   virtual evhCB_COMPL_STAT  ReplyCB(msgMESSAGE &msg, void *udata);
*   virtual evhCB_COMPL_STAT  ErrReplyCB(msgMESSAGE &msg, void *udata);
*   virtual evhCB_COMPL_STAT  TimeoutCB(msgMESSAGE &msg, void *udata);
*     Callbacks installed to normal replies, error reply and
*     timout to the transferred command.
*     They take care of resending replies to the command originator.
*      
*   ccsCOMPL_STAT SendReply(const char *newBuffer, 
*			    msgLENGTH   newBuflen=0,
*			    vltLOGICAL  lastReply=ccsTRUE);
*   ccsCOMPL_STAT SendReply(const ccsERROR &errmsg);
*     Sens back a reply (normal or error) to the originator of the
*     command to be rerouted.
*     If the reply to be sent is a last reply, the object itself
*     is deleted.
*
*   PRIVATE METHODS
*   waTRANSFER(const ccsENVNAME   destenv, 
*	       const ccsPROCNAME  destproc,
*	       const msgMESSAGE  &msg,
*	       const eccsTIMEVAL  interval);
*     Private constructor.
*
*   PRIVATE DATA MEMBERS
*   msgMESSAGE msg;
*     A local copy of the orginal message to be transferred
*
*   evhCOMMAND cmd;
*     Object to actually transfer thecommand to the final 
*     destination and handle the corresponding replies.
*
*   CAUTIONS 
*   The example is as complete and coherent as possible, 
*   but in order to keep the size limited some error handling
*   had to be skipped or simplified. 
*
*   A cleanup houskeeping is missing.
*   If the application terminates with some commands stil pending, 
*   they are not properly cleanup.
*   A static internal list of pending objects should be kept, to 
*   cleanup on exit, sending final error replies for each pending
*   transfer.
*   The implementation is standard, but would have made this example
*   more complex.
*
*   EXAMPLES
*
*   SEE ALSO
*
*   BUGS   
* 
*------------------------------------------------------------------------
*/

#define _POSIX_SOURCE 1
#include "vltPort.h"

static char *rcsId="@(#) $Id$"; 
static void *use_rcsId = ((void)&use_rcsId,(void *) &rcsId);


#include "waTRANSFER.h"

/**
 * Private contructor.
 */
waTRANSFER::waTRANSFER(const ccsENVNAME   destenv, 
		       const ccsPROCNAME  destproc,
		       const msgMESSAGE  &orgMsg,
		       const eccsTIMEVAL  interval): msg(orgMsg) 
{

    /*
     * Prepares the command to be sent based on the
     * original one and on the given parameters
     */
    msgMESSAGE transferMsg;

    transferMsg.Destenv(destenv);
    transferMsg.Destproc(destproc);
    transferMsg.Command(orgMsg.Command());
    transferMsg.Buffer(orgMsg.Buffer());
    
    /*
     * Configures the command handling object callbacks
     */
    evhOBJ_CALLBACK cb(this);
    cmd.Reply(cb.Proc((evhCB_METHOD)ReplyCB));
    cmd.ErrReply(cb.Proc((evhCB_METHOD)ErrReplyCB));
    cmd.Timeout(cb.Proc((evhCB_METHOD)TimeoutCB), interval);
    
    /*
     * Actually sends the command to the final destination
     */
    if(cmd.Send(transferMsg) == FAILURE)
      ErrAdd("eccs",eccsERR_GENERIC_TXT,__FILE_LINE__,
	     "Cannot transfer command");
}

/**
 * destructor
 */
waTRANSFER::~waTRANSFER()
{
    /*
     * If the object is still wayting for replies,
     * sends back an error reply and resets the callbacks
     */
    if(cmd.Status())
      {
      eccsLogComment("Transfer destroyed before command completion");
      ErrAdd("eccs",eccsERR_GENERIC_TXT,__FILE_LINE__,
	     "Transfer destroyed before command completion");
      if( msg.SendReply(ErrStack()) == FAILURE )
	{
	ErrAdd("evh",evhERR_SEND_REPLY,__FILE_LINE__);
	ErrStackClose();
	}
      cmd.Reset();
      }
}

/**
 * Factory method
 */
ccsCOMPL_STAT waTRANSFER::TransferCommand(const ccsENVNAME   destenv, 
					  const ccsPROCNAME  destproc,
					  const msgMESSAGE  &msg,
					  const eccsTIMEVAL  interval)
{
   /*
    * Creates the new instance
    */
    waTRANSFER *trf = new waTRANSFER(destenv, destproc, msg, interval);
  
   /*
    * If something goes wrong, cleanup
    */
    if(trf != NULL &&
       trf->ErrStatus() == FAILURE)
      {
      delete trf;
      trf = NULL;
      }
    return trf==NULL ? FAILURE : SUCCESS;
}

/**
 * Callbacks for replies from final destination.
 * The reply is rereouted to the originator
 */
evhCB_COMPL_STAT  waTRANSFER::ReplyCB(msgMESSAGE &transferMsg, void *)
{
    ErrReset();
    
    eccsLogComment("Reply to transfer Callback for command %.30s",
		   msg.Command());
    if(SendReply(transferMsg.Buffer(),
		 transferMsg.Buflen(),
		 transferMsg.LastReply()) == FAILURE)
      {
      ErrAdd("evh",evhERR_SEND_REPLY,__FILE_LINE__);
      ErrStackClose();
      }


    /*
     * The callback MUST not be deleted on return:
     * - If it was not the last reply, it is still needed
     * - If it was the last reply, the whole object has been
     *   already deleted and the callback removed by SendReply()
     */
    return evhCB_NO_DELETE;
}

evhCB_COMPL_STAT  waTRANSFER::ErrReplyCB(msgMESSAGE &transferMsg, void *)
{
    ErrReset();

    eccsLogComment("Error to transfer Callback.."); 
    if(SendReply(transferMsg.Errmsg()) == FAILURE)
      {
      ErrAdd("evh",evhERR_SEND_REPLY,__FILE_LINE__);
      ErrStackClose();
      }


    /*
     * The callback MUST not be deleted on return:
     * the whole object has been already deleted 
     * and the callback removed by SendReply()
     */
    return evhCB_NO_DELETE;
}

evhCB_COMPL_STAT  waTRANSFER::TimeoutCB(msgMESSAGE &, void *)
{
    ErrReset();

    eccsLogComment("Timeout to transfer Callback..");
    ErrAdd("eccs",eccsERR_GENERIC_TXT,__FILE_LINE__,
	   "Timeout in transfer callback");
    if(SendReply(ErrStack())==FAILURE)
      {
      ErrAdd("evh",evhERR_SEND_REPLY,__FILE_LINE__);
      ErrStackClose();
      }


    /*
     * The callback MUST not be deleted on return:
     * the whole object has been already deleted 
     * and the callback removed by SendReply()
     */
    return evhCB_NO_DELETE;
}

/**
 * Methods to send back a reply to the command originator
 */
ccsCOMPL_STAT waTRANSFER::SendReply(const char *newBuffer, 
				    msgLENGTH   newBuflen=0,
				    vltLOGICAL  lastReply=ccsTRUE)
{
    ErrReset();

    /*
     * Actually sends back the reply using the original message
     */
    if( msg.SendReply(newBuffer, newBuflen, lastReply) == FAILURE )
      {
      ErrAdd("evh",evhERR_SEND_REPLY,__FILE_LINE__);
      ErrStackClose();
      }

    /*
     * If it is the last reply, the object itself is deleted
     */
    if(lastReply)
      {
      /* 
       * The command object must be first reset, since the final
       * reply has been sent back.
       * If not, the object destructor would believe it has
       * not been sent and would send an error reply
       */
      cmd.Reset();
      delete this;
      }

    return SUCCESS;
}

ccsCOMPL_STAT waTRANSFER::SendReply(const ccsERROR &errmsg)
{
    ErrReset();

    /*
     * Actually sends back the error reply using the original message
     */
    if( msg.SendReply(errmsg) == FAILURE )
      {
      ErrAdd("evh",evhERR_SEND_REPLY,__FILE_LINE__);
      ErrStackClose();
      }

    /* 
     * The command object must be first reset, since the final
     * (error) reply has been sent back.
     * If not, the object destructor would believe it has
     * not been sent and would send an error reply
     */
    cmd.Reset();
    delete this;

    return SUCCESS;
}

/*___oOo___*/
