/*
 * @(#)file      SnmpInformRequest.java
 * @(#)author    Sun Microsystems, Inc.
 * @(#)version   1.19
 * @(#)date      10/06/22
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */

package com.sun.jmx.snmp.daemon ;


// JAVA imports
//
import java.io.Serializable;
import java.net.InetAddress;
import java.util.Vector;
import java.util.Date;

// JMX imports
//
import com.sun.jmx.snmp.SnmpMessage;
import com.sun.jmx.snmp.SnmpVarBind;
import com.sun.jmx.snmp.SnmpPduFactory;
import com.sun.jmx.snmp.SnmpPduPacket;
import com.sun.jmx.snmp.SnmpPduRequest;
import com.sun.jmx.snmp.SnmpPduBulk;
import com.sun.jmx.snmp.SnmpDefinitions;
import com.sun.jmx.snmp.SnmpStatusException;
import com.sun.jmx.snmp.SnmpTooBigException;
import com.sun.jmx.snmp.SnmpVarBindList;
import com.sun.jmx.snmp.SnmpPdu;
import com.sun.jmx.snmp.SnmpPduRequestType;

// SNMP Runtime imports
//
import com.sun.jmx.trace.Trace;


/**
 * This class is used by the {@link com.sun.jmx.snmp.daemon.SnmpAdaptorServer SNMP adaptor server} to send inform requests
 * to an SNMP manager and receive inform responses.
 * <P>
 * This class provides basic functions that enable you to fire inform requests,
 * handle retries, timeouts, and process responses from the manager.
 * <BR>
 * The SNMP adaptor server specifies the destination of the inform request and controls
 * the size of a single inform request/response to fit into its <CODE>bufferSize</CODE>.
 * It specifies the maximum number of tries and the timeout to be used for the inform requests.
 * It also provides resources such as the authentication mechanism (using its PDU factory), 
 * controlling all inform requests created by it, and finally the inform response to the user.
 * <P>
 * Each inform request, when ready to be sent, is assigned a unique identifier which helps
 * in identifying the inform request with matching inform responses to the protocol engine 
 * lying transparently underneath. The engine does the job of retrying the inform requests
 * when the timer expires and calls the SNMP adaptor server when a timeout occurs after exhausting 
 * the maximum number of tries.
 * <P>
 * The inform request object provides the method, {@link #waitForCompletion waitForCompletion(long time)},
 * which enables a user to operate in a synchronous mode with an inform request.
 * This is done by blocking the user thread for the desired time interval.
 * The user thread gets notified whenever a request reaches completion, independently of the status of the response. 
 * <P>
 * If an {@link com.sun.jmx.snmp.daemon.SnmpInformHandler inform callback} is provided when sending the inform request,
 * the user operates in an asynchronous mode with the inform request. The user thread is not blocked
 * and the specific inform callback implementation provided by the user is invoked when the inform response is received.
 *
 * <P>
 * <B>Note:</B>
 * <BR>From RFC 1905, the SNMP inform request is defined as a request generated and transmitted
 * by an SNMPv2 entity acting in a manager role to another SNMPv2 entity also acting in a manager role.
 * The mechanisms to implement this behaviour are defined in the SNMP manager API.
 * <BR>
 * Nevertheless, this feature has derived and in some documentations, the inform request appears
 * like an SNMPv2 trap that gets responded.
 * <BR>The <CODE>SnmpInformRequest</CODE> class is used to fullfill this latter case.
 * <p><b>This API is a Sun Microsystems internal API  and is subject 
 * to change without notice.</b></p>
 */

public class SnmpInformRequest implements SnmpDefinitions {
    
    // VARIABLES
    //----------
      
    /**
     * This object maintains a global counter for the inform request ID.
     */
    private static SnmpRequestCounter requestCounter = new SnmpRequestCounter();
    
    /**
     * This contains a list of <CODE>SnmpVarBind</CODE> objects for making the SNMP inform requests.
     */
    private SnmpVarBindList varBindList = null;
    
    /**
     * The error status associated with the inform response packet.
     */
    int errorStatus = 0;	
    
    /**
     * The index in <CODE>SnmpVarBindList</CODE> that caused the exception.
     */
    int errorIndex = 0;	
    
    //private SnmpVarBind internalVarBind[] = null;
    SnmpVarBind internalVarBind[] = null;

    //private String reason = null;
    String reason = null;
        
    /**
     * The SNMP adaptor associated with this inform request.
     */
    private transient SnmpAdaptorServer adaptor;
        
    /**
     * The session object associated with this inform request.
     */
    private transient SnmpSession informSession;
        
    /**
     * The user implementation of the callback interface for this request.
     */
    private SnmpInformHandler callback = null;
        
    /**
     * The inform request PDU.
     */
    //private SnmpPduPacket requestPdu;
    SnmpPdu requestPdu;

    /**
     * The inform response PDU.
     */
    //private SnmpPduRequest responsePdu;
    SnmpPduRequestType responsePdu;
    
    /**
     * Base status of an inform request.
     */
    final static private int stBase             = 1;
    
    /**
     * Status of an inform request: in progress.
     */
    final static public int stInProgress 		= stBase;
  
    /**
     * Status of an inform request: waiting to be sent.
     */
    final static public int stWaitingToSend 	= (stBase << 1) | stInProgress;
  
    /**
     * Status of an inform request: waiting for reply.
     */
    final static public int stWaitingForReply 	= (stBase << 2) | stInProgress;
  
    /**
     * Status of an inform request: reply received.
     */
    final static public int stReceivedReply 	= (stBase << 3) | stInProgress;
  
    /**
     * Status of an inform request: request aborted.
     */
    final static public int stAborted 			= (stBase << 4);
  
    /**
     * Status of an inform request: timeout.
     */
    final static public int stTimeout 			= (stBase << 5);
  
    /**
     * Status of an inform request: internal error occured.
     */
    final static public int stInternalError 	= (stBase << 6);
  
    /**
     * Status of an inform request: result available for the request.
     */
    final static public int stResultsAvailable 	= (stBase << 7);
  
    /**
     * Status of an inform request: request never used.
     */
    final static public int stNeverUsed 		= (stBase << 8);
        
    /**
     * Number of tries performed for the current polling operation.
     */
    private int numTries = 0;

    /**
     * Timeout.
     * The default amount of time is 3000 millisec.
     */
    private int timeout = 3 * 1000; // 3 seconds.

    /**
     */
    private int reqState = stNeverUsed;

    // Polling control parameters.
    private long  prevPollTime = 0;	// value of 0 means poll never happened.
    private long  nextPollTime = 0;
    private long  waitTimeForResponse;
    private Date debugDate = new Date();

    /**
     * The request ID for an active inform request.
     */
    private int requestId = 0;	
    
    private int port = 0;

    private InetAddress address = null;
    private String communityString = null;
    
    String dbgTag = "SnmpInformRequest";
    
    // CONSTRUCTORS
    //-------------

    /**
     * For SNMP Runtime internal use only.
     * Constructor for creating new inform request. This object can be created only by an SNMP adaptor object. 
     * @param session <CODE>SnmpSession</CODE> object for this inform request.
     * @param adp <CODE>SnmpAdaptorServer</CODE> object for this inform request.
     * @param addr The <CODE>InetAddress</CODE> destination for this inform request.
     * @param cs The community string to be used for the inform request.
     * @param requestCB Callback interface for the inform request.
     * @exception SnmpStatusException SNMP adaptor is not ONLINE or session is dead.
     */
    SnmpInformRequest(SnmpSession session, 
		      SnmpAdaptorServer adp, 
		      InetAddress addr, 
		      String cs, 
		      int p,
		      SnmpInformHandler requestCB) 
        throws SnmpStatusException {
        
        informSession = session;
        adaptor = adp;
        address = addr;
        communityString = cs;
	port = p;
        callback = requestCB;
        informSession.addInformRequest(this);  // add to adaptor queue.
        setTimeout(adaptor.getTimeout()) ;
    }

    // PUBLIC METHODS
    //---------------

    /**
     * Gets the request id (invoke identifier) of the current inform request.
     * @return The request id.
     */
    final public synchronized int getRequestId () {
        return requestId;
    }

    /**
     * Gets the destination address of the current inform request.
     * @return The destination address.
     */
    synchronized InetAddress getAddress() {
        return address;
    }

    /**
     * Gets the current status of the inform request.
     * @return The current status of the inform request.
     */
    final public synchronized int getRequestStatus() {
        return reqState ;
    }
    
    /**
     * Indicates whether or not the inform request was aborted.
     * @return <CODE>true</CODE> if the inform request was aborted, <CODE>false</CODE> otherwise.
     */
    final public synchronized boolean isAborted() {
        return ((reqState & stAborted) == stAborted);
    }

    /**
     * Indicates whether or not the inform request is in progress.
     * @return <CODE>true</CODE> if the inform request is in progress, <CODE>false</CODE> otherwise.
     */
    final public synchronized boolean inProgress() {
        return ((reqState & stInProgress) == stInProgress);
    }

    /**
     * Indicates whether or not the inform request result is available.
     * @return <CODE>true</CODE> if the inform request result is available, <CODE>false</CODE> otherwise.
     */
    final public synchronized boolean isResultAvailable() {
        return (reqState == stResultsAvailable);
    }
    
    /**
     * Gets the status associated with the <CODE>SnmpVarBindList</CODE>.
     * @return The error status.
     */
    final public synchronized int getErrorStatus() {
        return errorStatus;
    }

    /**
     * Gets the index.
     * <P>NOTE: this value is equal to the <CODE>errorIndex</CODE> field minus 1. 
     * @return The error index.
     */
    final public synchronized int getErrorIndex() {
        return errorIndex;
    }

    /**
     * Gets the maximum number of tries before declaring that the manager is not responding.
     * @return The maximum number of times an inform request should be tried.
     */
    final public int getMaxTries() {
        return adaptor.getMaxTries();
    }
    
    /**
     * Gets the number of tries performed for the current inform request.
     * @return The number of tries performed.
     */
    final public synchronized int getNumTries() {
        return numTries ;
    }
    
    /**
     * For SNMP Runtime internal use only.
     */
    final synchronized void setTimeout(int value) {
        timeout = value ;
    }
    
    /**
     * Gets absolute time in milliseconds (based on epoch time) when the next
     * polling activity will begin.
     * @return The absolute time when polling will begin.
     */
    final public synchronized long getAbsNextPollTime () {
        return nextPollTime ;
    }
    
    /**
     * Gets absolute time in milliseconds (based on epoch time) before which an inform
     * response is expected from a manager.
     * @return The absolute time within which an inform response is expected.
     */
    final public synchronized long getAbsMaxTimeToWait() {
        if (prevPollTime == 0) {
            return System.currentTimeMillis() ;  // should never happen.
        } else {
            return waitTimeForResponse ;
        }
    }
    
    /**
     * Gets the <CODE>SnmpVarBindList</CODE> of the inform response.
     * It returns a null value if the inform request is in progress. 
     * This ensures accidental manipulation does not occur when a request is in progress. 
     * In case of an error, <CODE>SnmpVarBindList</CODE> is the copy 
     * of the original <CODE>SnmpVarBindList</CODE> at the time of making the inform request.
     * @return The list of <CODE>SnmpVarBind</CODE> objects returned by the manager or the null value if the request
     * is in progress.
     */
    public final synchronized SnmpVarBindList getResponseVarBindList() {
        if (inProgress())
            return null;
        return varBindList;
    }
            
    /**
     * Used in synchronous mode only.
     * Provides a hook that enables a synchronous operation on a previously sent inform request.
     * Only one inform request can be in synchronous mode on a given thread.
     * The blocked thread is notified when the inform request state reaches completion. 
     * If the inform request is not active, the method returns immediately.
     * The user must get the error status of the inform request to determine the
     * exact status of the request.
     * 
     * @param time The amount of time to wait. Zero means block until complete.
     * @return <CODE>true</CODE> if the inform request has completed, <CODE>false</CODE> if it is still active.
     */
    final public boolean waitForCompletion(long time) {
        
        if (! inProgress())     // check if request is in progress.
            return true;

        if (informSession.thisSessionContext()) {
            // We can manipulate callback safely as we are in session thread.
            //
            SnmpInformHandler savedCallback = callback;
            callback = null;
            informSession.waitForResponse(this, time);
            callback = savedCallback;
        } else {
            // This is being done from a different thread. So notifyClient will do the notification.
            //
            synchronized (this) {
                SnmpInformHandler savedCallback = callback ;
                try {
                    callback = null ;
                    this.wait(time) ;
                } catch (InterruptedException e) {
                }
                callback = savedCallback ;
            }
        }
		
        return (! inProgress()); // true if request completed.
    }

    /**
     * Cancels the active inform request and removes itself from the polling list.
     */
    final public void cancelRequest() {
        errorStatus = snmpReqAborted;  
        stopRequest();
        deleteRequest();
        notifyClient();
    }

    /**
     * Notifies the registered client about the completion of an operation.
     */
    final public synchronized void notifyClient() {
        this.notifyAll();
    }

    /**
     * Finalizer of the <CODE>SnmpInformRequest</CODE> objects.
     * This method is called by the garbage collector on an object 
     * when garbage collection determines that there are no more references to the object.
     * <P>Sets all the references to this SNMP inform request object to <CODE>null</CODE>.
     */
    public void finalize() {
        callback = null;
        varBindList = null;
        internalVarBind = null;
        adaptor = null;
        informSession = null;
        requestPdu = null;
	responsePdu = null;
    }

    /**
     * Returns the <CODE>String</CODE> representation of an error code.
     * @param errcode The error code as an integer.
     * @return The error code as a <CODE>String</CODE>.
     */
    public static String snmpErrorToString(int errcode) {
        switch (errcode) {
        case snmpRspNoError :
            return "noError" ;
        case snmpRspTooBig :
            return "tooBig" ;
        case snmpRspNoSuchName :
            return "noSuchName" ;
        case snmpRspBadValue :
            return "badValue" ;
        case snmpRspReadOnly :
            return "readOnly" ;
        case snmpRspGenErr :
            return "genErr" ;
        case snmpRspNoAccess :
            return "noAccess" ;
        case snmpRspWrongType :
            return "wrongType" ;
        case snmpRspWrongLength :
            return "wrongLength" ;
        case snmpRspWrongEncoding :
            return "wrongEncoding" ;
        case snmpRspWrongValue :
            return "wrongValue" ;
        case snmpRspNoCreation :
            return "noCreation" ;
        case snmpRspInconsistentValue :
            return "inconsistentValue" ;
        case snmpRspResourceUnavailable :
            return "resourceUnavailable" ;
        case snmpRspCommitFailed :
            return "commitFailed" ;
        case snmpRspUndoFailed :
            return "undoFailed" ;
        case snmpRspAuthorizationError :
            return "authorizationError" ;
        case snmpRspNotWritable :
            return "notWritable" ;
        case snmpRspInconsistentName :
            return "inconsistentName" ;
        case snmpReqTimeout :
            return "reqTimeout" ;
        case snmpReqAborted :
            return "reqAborted" ;
        case snmpRspDecodingError :
            return "rspDecodingError" ;
        case snmpReqEncodingError :
            return "reqEncodingError" ;
        case snmpReqPacketOverflow :
            return "reqPacketOverflow" ;
        case snmpRspEndOfTable :
            return "rspEndOfTable" ;
        case snmpReqRefireAfterVbFix :
            return "reqRefireAfterVbFix" ;
        case snmpReqHandleTooBig :
            return "reqHandleTooBig" ;
        case snmpReqTooBigImpossible :
            return "reqTooBigImpossible" ;
        case snmpReqInternalError :
            return "reqInternalError" ;
        case snmpReqSocketIOError :
            return "reqSocketIOError" ;
        case snmpReqUnknownError :
            return "reqUnknownError" ;
        case snmpWrongSnmpVersion :
            return "wrongSnmpVersion" ;
	case snmpUnknownPrincipal:
	    return "snmpUnknownPrincipal";
	case snmpAuthNotSupported:
	    return "snmpAuthNotSupported";
	case snmpPrivNotSupported:
	    return "snmpPrivNotSupported";
	case snmpBadSecurityLevel:
	    return "snmpBadSecurityLevel";
	case snmpUsmBadEngineId:
	    return "snmpUsmBadEngineId";   
	case snmpUsmInvalidTimeliness:
	    return "snmpUsmInvalidTimeliness";
        }
        return "Unknown Error = " + errcode;
    }
    
    // PRIVATE AND PACKAGE METHODS
    //----------------------------
    
    /**
     * For SNMP Runtime internal use only.
     * Starts an inform request in asynchronous mode. The callback interface
     * is used to notify the user upon request completion.
     * @param vblst The list of <CODE>SnmpVarBind</CODE> to be used.
     * @exception SnmpStatusException This inform request is already in progress.
     */
    synchronized void start(SnmpVarBindList vblst) throws SnmpStatusException {
        if (inProgress())
            throw  new SnmpStatusException("Inform request already in progress.");
        setVarBindList(vblst);
        initializeAndFire();
    }

    private synchronized void initializeAndFire() {
        requestPdu = null;
	responsePdu = null;
        reason = null;
        startRequest(System.currentTimeMillis());
        setErrorStatusAndIndex(0, 0);
    }
    
    /**
     * This method submits the inform request for polling and marks the request
     * active. It does nothing if the request is already active.
     * The poll will be scheduled to happen immediately.
     * @param starttime The start time for polling.
     */
    private synchronized void startRequest(long starttime) {
        nextPollTime = starttime;
        prevPollTime = 0;
        schedulePoll();
    }
    
    /**
     * This method creates a new request ID. The ID is submitted to the poll server for scheduling.
     */
    private void schedulePoll() {
        numTries = 0;
        initNewRequest();
        setRequestStatus(stWaitingToSend);
        informSession.getSnmpQManager().addRequest(this);
    }
    
    /**
     * This method determines whether the inform request is to be retried. This is used if the
     * peer did not respond to a previous request. If the request exceeds 
     * the maxTries limit, a timeout is signaled.
     */
    void action() {
        if (inProgress() == false)
            return;
        while (true) {
            try {
                if (numTries == 0) {
                    invokeOnReady();
                } else if (numTries < getMaxTries()) {
                    invokeOnRetry();
                } else {
                    invokeOnTimeout();
                }
                return ;
            } catch (OutOfMemoryError omerr) {
                // Consider it as a try !
                //
                numTries++;
                if (isDebugOn()) {
                    debug("action", "Inform request hit out of memory situation...");
                }
                Thread.currentThread().yield();
            }
        }
    }
    
    final private void invokeOnReady() {
        if (requestPdu == null) {
            requestPdu = constructPduPacket();
        }
        if (requestPdu != null) {
            if (sendPdu() == false)
                queueResponse();
        }
    }

    final private void invokeOnRetry() {  
        invokeOnReady();
    }
    
    final private void invokeOnTimeout() {
        errorStatus = snmpReqTimeout; 
        queueResponse();
    }
    
    final private void queueResponse() {
        informSession.addResponse(this);
    }
    
    /**
     * Constructs an inform request PDU.
     */
    synchronized SnmpPdu constructPduPacket() {  
        SnmpPduPacket reqpdu = null;
        Exception excep = null;
        try {
            reqpdu = new SnmpPduRequest();
            reqpdu.port = port;
            reqpdu.type = pduInformRequestPdu;
            reqpdu.version = snmpVersionTwo;
            reqpdu.community = communityString.getBytes("8859_1");
            reqpdu.requestId = getRequestId();
            reqpdu.varBindList = internalVarBind;

            if (isTraceOn()) {
                trace("constructPduPacket", "Packet built");
            }

        } catch (Exception e) {
            excep = e;
            errorStatus = snmpReqUnknownError;
            reason = e.getMessage();
        } 
        if (excep != null) {
            if (isDebugOn()) {
                debug("constructPduPacket", excep);
            }
            reqpdu = null;
            queueResponse();
        }
        return reqpdu;
    }
    
    boolean sendPdu() {
        try {
            responsePdu = null;
      
            SnmpPduFactory pduFactory = adaptor.getPduFactory();
            SnmpMessage msg = (SnmpMessage)pduFactory.encodeSnmpPdu((SnmpPduPacket)requestPdu, adaptor.getBufferSize().intValue());

            if (msg == null) {
                if (isDebugOn()) {
                    debug("sendPdu", "pdu factory returned a null value");
                }
                throw new SnmpStatusException(snmpReqUnknownError);
                // This exception will caught hereafter and reported as an snmpReqUnknownError
                // FIXME: may be it's not the best behaviour ?
            }

            int maxPktSize = adaptor.getBufferSize().intValue();
            byte[] encoding = new byte[maxPktSize];
            int encodingLength = msg.encodeMessage(encoding);
      
            if (isTraceOn()) {
                trace("sendPdu", "Dump : \n" + msg.printMessage());
            }

            sendPduPacket(encoding, encodingLength);
            return true;
        } catch (SnmpTooBigException ar) {
    
            if (isDebugOn()) {
                debug("sendPdu", ar);
            }
      
            setErrorStatusAndIndex(snmpReqPacketOverflow, ar.getVarBindCount());
            requestPdu = null;
            reason = ar.getMessage();
            if (isDebugOn()) {
                debug("sendPdu", "Packet Overflow while building inform request");
            }
        } catch (java.io.IOException ioe) {
            setErrorStatusAndIndex(snmpReqSocketIOError, 0);
            reason = ioe.getMessage();
        } catch (Exception e) {
            if (isDebugOn()) {
                debug("sendPdu", e);
            }
            setErrorStatusAndIndex(snmpReqUnknownError, 0);
            reason = e.getMessage();
        }
        return false;
    }

    /**
     * Sends the prepared PDU packet to the manager and updates the data structure
     * to expect a response. It acquires a lock on the socket to prevent a case
     * where a response arrives before this thread could insert the
     * request into the wait queue.
     * @exception IOException Signals that an I/O exception of some sort has occurred.
     */
    final void sendPduPacket(byte[] buffer, int length) throws java.io.IOException {
    
        if (isTraceOn()) {
            trace("sendPduPacket", "Send to peer. Peer/Port : " + address.getHostName() + "/" + port + 
                  ". Length = " +  length + "\nDump : \n" + SnmpMessage.dumpHexBuffer(buffer,0, length));
        }
        SnmpSocket theSocket = informSession.getSocket();
        synchronized (theSocket) {
            theSocket.sendPacket(buffer, length, address, port);
            setRequestSentTime(System.currentTimeMillis());
        }
    }

    /**
     * For SNMP Runtime internal use only.
     */
    final void processResponse() {
    
        if (isTraceOn()) {
            trace("processResponse", "errstatus = " + errorStatus);
        }

        if (inProgress() == false) {  // check if this request is still alive.
            responsePdu = null;
            return;  // the request may have  cancelled.
        }

        if (errorStatus >= snmpReqInternalError) {
            handleInternalError("Internal Error...");
            return;
        }

        try {
            parsePduPacket(responsePdu);
            //responsePdu = null;

            // At this point the errorIndex is rationalized to start with 0.
            switch (errorStatus) {
            case snmpRspNoError : 
                handleSuccess();
                return;
            case snmpReqTimeout : 
                handleTimeout();
                return;
            case snmpReqInternalError : 
                handleInternalError("Unknown internal error.  deal with it later!");
                return;
            case snmpReqHandleTooBig :
                setErrorStatusAndIndex(snmpRspTooBig, 0);
                handleError("Cannot handle too-big situation...");
                return;
            case snmpReqRefireAfterVbFix :
                // Refire request after fixing varbindlist.
                initializeAndFire();
                return;
            default :
                handleError("Error status set in packet...!!");
                return;
            }
        } catch (Exception e) {
            if (isDebugOn()) {
                debug("processResponse", e);
            }
            reason = e.getMessage();
        } 
        handleInternalError(reason);
    }
    
    /**
     * Parses the inform response packet. If the agent responds with error set,
     * it does not parse any further.
     */
    synchronized void parsePduPacket(SnmpPduRequestType rpdu) {
        
        if (rpdu == null)
            return;
	
        errorStatus = rpdu.getErrorStatus();
        errorIndex = rpdu.getErrorIndex();
	
        if (errorStatus == snmpRspNoError) {
            updateInternalVarBindWithResult(((SnmpPdu)rpdu).varBindList);
            return;
        }
	
        if (errorStatus != snmpRspNoError)  
            --errorIndex;  // rationalize for index to start with 0.
	
        if (isTraceOn()) {
            trace("parsePduPacket", "received inform response. ErrorStatus/ErrorIndex = " + errorStatus + "/" + errorIndex);
        }
    }
    
    /**
     * Calls the user implementation of the <CODE>SnmpInformHandler</CODE> interface.
     */
    private void handleSuccess() {
        
        setRequestStatus(stResultsAvailable);

        if (isTraceOn()) {
            trace("handleSuccess", "Invoking user defined callback...");
        }

        deleteRequest();  // delete only non-poll request.
        notifyClient();
		
        requestPdu = null;
	//responsePdu = null;
        internalVarBind = null;

        try {  // catch all user exception which may happen in callback.
            if (callback != null)
                callback.processSnmpPollData(this, errorStatus, errorIndex, getVarBindList());
        } catch (Exception e) {
            if (isDebugOn()) {
                debug("handleSuccess", "Exception generated by user callback");
                debug("handleSuccess", e);
            }
        } catch (OutOfMemoryError ome) {
            if (isDebugOn()) {
                debug("handleSuccess", "OutOfMemory Error generated by user callback");
                debug("handleSuccess", ome);
            }
            Thread.currentThread().yield();
        }
        return;
    }

    /**
     * Calls the user implementation of the <CODE>SnmpInformHandler</CODE> interface.
     */
    private void handleTimeout() {
        
        setRequestStatus(stTimeout);
        
        if (isDebugOn()) {
            debug("handleTimeout", "Snmp error/index = " + snmpErrorToString(errorStatus) + "/" +
                  errorIndex + ". Invoking timeout user defined callback...");
        }
        deleteRequest();
        notifyClient();

        requestPdu = null;
	responsePdu = null;
        internalVarBind = null;

        try {
            if (callback != null) 
                callback.processSnmpPollTimeout(this);
        } catch (Exception e) {  // catch any exception a user might not handle.
            if (isDebugOn()) {
                debug("handleTimeout", "Exception generated by user callback");
                debug("handleTimeout", e);
            }
        } catch (OutOfMemoryError ome) {
            if (isDebugOn()) {
                debug("handleTimeout", "OutOfMemory Error generated by user callback");
                debug("handleTimeout", ome);
            }
            Thread.currentThread().yield();
        }
        return;
    }
  
    /**
     * Calls the user implementation of the <CODE>SnmpInformHandler</CODE> interface.
     */
    private void handleError(String msg) {
        
        setRequestStatus(stResultsAvailable);

        if (isDebugOn()) {
            debug("handleError", "Snmp error/index = " + snmpErrorToString(errorStatus) + "/" +
                  errorIndex + ". Invoking error user defined callback...\n" + getVarBindList());
        }
        deleteRequest();
        notifyClient();
		
        requestPdu = null;
	responsePdu = null;
        internalVarBind = null;
		
        try {
            if (callback != null)
                callback.processSnmpPollData(this, getErrorStatus(), getErrorIndex(), getVarBindList());
        } catch (Exception e) {  // catch any exception a user might not handle.
            if (isDebugOn()) {
                debug("handleError", "Exception generated by user callback");
                debug("handleError", e);
            }
        } catch (OutOfMemoryError ome) {
            if (isDebugOn()) {
                debug("handleError", "OutOfMemory Error generated by user callback");
                debug("handleError", ome);
            }
            Thread.currentThread().yield();
        }
    }

    /**
     * Calls the user implementation of the <CODE>SnmpInformHandler</CODE> interface.
     */
    private void handleInternalError(String msg) {
        
        setRequestStatus(stInternalError);
        if (reason == null)
            reason = msg;

        if (isDebugOn()) {
            debug("handleInternalError", "Snmp error/index = " + snmpErrorToString(errorStatus) + "/" +
                  errorIndex + ". Invoking internal error user defined callback...\n" + getVarBindList());
        }

        deleteRequest();
        notifyClient();

        requestPdu = null;
	responsePdu = null;
        internalVarBind = null;
		
        try {
            if (callback != null) 
                callback.processSnmpInternalError(this, reason);
        } catch (Exception e) {  // catch any exception a user might not handle.
            if (isDebugOn()) {
                debug("handleInternalError", "Exception generated by user callback");
                debug("handleInternalError", e);
            }
        } catch (OutOfMemoryError ome) {
            if (isDebugOn()) {
                debug("handleInternalError", "OutOfMemory Error generated by user callback");
                debug("handleInternalError", ome);
            }
            Thread.currentThread().yield();
        }
    }

    void updateInternalVarBindWithResult(SnmpVarBind[] list) {
     
        if ((list == null) || (list.length == 0))
            return;
    
        int idx = 0;
        
        for(int i = 0; i < internalVarBind.length && idx < list.length; i++) {
            SnmpVarBind avar = internalVarBind[i];
            if (avar == null)
                continue;
                
            SnmpVarBind res = list[idx];
            avar.setSnmpValue(res.getSnmpValue());
            idx++;
        }
    }
    
    /**
     * For SNMP Runtime internal use only.
     */
    final void invokeOnResponse(Object resp) {
        if (resp != null) {
            if (resp instanceof SnmpPduRequestType) 
                responsePdu = (SnmpPduRequestType) resp;
            else 
                return;
        }
        setRequestStatus(stReceivedReply);
        queueResponse();
    }
    
    /**
     * This method cancels an active inform request and removes it from the polling list.
     */
    private void stopRequest() {
        
        // Remove the clause synchronized of the stopRequest method.
        // Synchronization is isolated as possible to avoid thread lock.
        // Note: the method removeRequest from SendQ is synchronized.
        // fix bug jaw.00392.B
        //
        synchronized(this) {
            setRequestStatus(stAborted);
        }
        informSession.getSnmpQManager().removeRequest(this);
        synchronized(this) {
            requestId = 0;
        }
    }
    
    final synchronized void deleteRequest() {
        informSession.removeInformRequest(this);
    }
    
    /**
     * For SNMP Runtime internal use only.
     * Gets the active <CODE>SnmpVarBindList</CODE>. The contents of it
     * are not guaranteed to be consistent when the inform request is active.
     * @return The list of <CODE>SnmpVarBind</CODE> objects.
     */
    final synchronized SnmpVarBindList getVarBindList() {
        return varBindList;
    }

    /**
     * For SNMP Runtime internal use only.
     * You should specify the <CODE>SnmpVarBindList</CODE> at SnmpInformRequest creation time. 
     * You cannot modify it during the life-time of the object.
     */
    final synchronized void setVarBindList(SnmpVarBindList newvblst) {
        varBindList = newvblst;
        if (internalVarBind == null || internalVarBind.length != varBindList.size()) {
            internalVarBind = new SnmpVarBind[varBindList.size()];
        }
        varBindList.copyInto(internalVarBind);
    }

    /**
     * For SNMP Runtime internal use only.
     */
    final synchronized void setErrorStatusAndIndex(int stat, int idx) {
        errorStatus = stat;
        errorIndex = idx;
    }
    
    /**
     * For SNMP Runtime internal use only.
     */
    final synchronized void setPrevPollTime(long prev) {
        prevPollTime = prev;
    }

    /**
     * For SNMP Runtime internal use only.
     */
    final  void setRequestSentTime(long sendtime) {
        numTries++;
        setPrevPollTime(sendtime);
        waitTimeForResponse = prevPollTime + timeout*numTries;
        setRequestStatus(stWaitingForReply);
    
        if (isTraceOn()) {
            trace("setRequestSentTime", "Inform request Successfully sent");
        }
    
        informSession.getSnmpQManager().addWaiting(this);
    }

    /**
     * Initializes the request id from the request counter.
     */
    final synchronized void initNewRequest() {
        requestId = requestCounter.getNewId();
    }

    /**
     * For SNMP Runtime internal use only.
     */
    long timeRemainingForAction(long currtime) {
        switch (reqState) {
        case stWaitingToSend :
            return nextPollTime - currtime;
        case stWaitingForReply :
            return waitTimeForResponse - currtime;
        default :
            return -1;
        }
    }

    /**
     * Returns the string state corresponding to the specified integer state.
     * @param state The integer state.
     * @return The string state.
     */
    final static String statusDescription(int state) {
        switch (state) {
        case stWaitingToSend :
            return "Waiting to send.";
        case stWaitingForReply :
            return "Waiting for reply.";
        case stReceivedReply :
            return "Response arrived.";
        case stAborted  :
            return "Aborted by user.";
        case stTimeout :
            return "Timeout Occured.";
        case stInternalError :
            return "Internal error.";
        case stResultsAvailable :
            return "Results available";
        case stNeverUsed :
            return "Inform request in createAndWait state";
        }
        return "Unknown inform request state.";
    }

    /**
     * Sets the request status to the specified value.
     * @param reqst The new status request.
     */
    final synchronized void setRequestStatus(int reqst) {
        reqState = reqst;
    }

    /**
     * Gives a status report of the request.
     * @return The status report of the request.
     */
    public synchronized String toString() {
        StringBuffer s = new StringBuffer(300) ;
        s.append(tostring()) ;
        s.append("\nPeer/Port : " + address.getHostName() + "/" + port) ;

        return s.toString() ;
    }
    
    private synchronized String tostring() {
        StringBuffer s = new StringBuffer("InformRequestId = " + requestId);
        s.append("   " + "Status = " + statusDescription(reqState));
        s.append("  Timeout/MaxTries/NumTries = " + timeout*numTries + "/" +
                 + getMaxTries() + "/" + numTries);

        if (prevPollTime > 0) {
            debugDate.setTime(prevPollTime);
            s.append("\nPrevPolled = " + debugDate.toString());
        } else
            s.append("\nNeverPolled");
        s.append(" / RemainingTime(millis) = " + 
                 timeRemainingForAction(System.currentTimeMillis()));

        return s.toString();
    }

    // TRACES & DEBUG
    //---------------
    
    boolean isTraceOn() {
        return Trace.isSelected(Trace.LEVEL_TRACE, Trace.INFO_ADAPTOR_SNMP);
    }

    void trace(String clz, String func, String info) {
        Trace.send(Trace.LEVEL_TRACE, Trace.INFO_ADAPTOR_SNMP, clz, func, info);
    }

    void trace(String func, String info) {
        trace(dbgTag, func, info);
    }
    
    boolean isDebugOn() {
        return Trace.isSelected(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP);
    }

    void debug(String clz, String func, String info) {
        Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP, clz, func, info);
    }

    void debug(String clz, String func, Throwable exception) {
        Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP, clz, func, exception);
    }
    
    void debug(String func, String info) {
        debug(dbgTag, func, info);
    }
    
    void debug(String func, Throwable exception) {
        debug(dbgTag, func, exception);
    }
    
}
