Package gov.nist.javax.sip.stack

Source Code of gov.nist.javax.sip.stack.SIPTransactionStack

/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain.  As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY.  NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
package gov.nist.javax.sip.stack;

import gov.nist.core.Host;
import gov.nist.core.HostPort;
import gov.nist.core.LogWriter;
import gov.nist.core.ServerLogger;
import gov.nist.core.StackLogger;
import gov.nist.core.ThreadAuditor;
import gov.nist.core.net.AddressResolver;
import gov.nist.core.net.DefaultNetworkLayer;
import gov.nist.core.net.NetworkLayer;
import gov.nist.javax.sip.DefaultAddressResolver;
import gov.nist.javax.sip.ListeningPointImpl;
import gov.nist.javax.sip.LogRecordFactory;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.SipListenerExt;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.header.Event;
import gov.nist.javax.sip.header.Via;
import gov.nist.javax.sip.header.extensions.JoinHeader;
import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import gov.nist.javax.sip.parser.MessageParserFactory;
import gov.nist.javax.sip.stack.timers.SipTimer;

import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.DialogTerminatedEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipListener;
import javax.sip.TransactionState;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.address.Hop;
import javax.sip.address.Router;
import javax.sip.header.CallIdHeader;
import javax.sip.header.EventHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

/*
* Jeff Keyser : architectural suggestions and contributions. Pierre De Rop and Thomas Froment :
* Bug reports. Jeyashankher < jai@lucent.com > : bug reports. Jeroen van Bemmel : Bug fixes.
*
*
*/

/**
*
* This is the sip stack. It is essentially a management interface. It manages
* the resources for the JAIN-SIP implementation. This is the structure that is
* wrapped by the SipStackImpl.
*
* @see gov.nist.javax.sip.SipStackImpl
*
* @author M. Ranganathan <br/>
*
* @version 1.2 $Revision: 1.163 $ $Date: 2010/07/13 00:12:31 $
*/
public abstract class SIPTransactionStack implements
    SIPTransactionEventListener, SIPDialogEventListener {

    /*
     * Number of milliseconds between timer ticks (500).
     */
    public static final int BASE_TIMER_INTERVAL = 500;

    /*
   * Connection linger time (seconds) this is the time (in seconds) for which
   * we linger the TCP connection before closing it.
     */
    public static final int CONNECTION_LINGER_TIME = 8;
   
    /*
     * Dialog Early state timeout duration.
     */
    protected int earlyDialogTimeout = 180;
   

    /*
     * Table of retransmission Alert timers.
     */
    protected ConcurrentHashMap<String, SIPServerTransaction> retransmissionAlertTransactions;

    // Table of early dialogs ( to keep identity mapping )
    protected ConcurrentHashMap<String, SIPDialog> earlyDialogTable;

    // Table of dialogs.
    protected ConcurrentHashMap<String, SIPDialog> dialogTable;

  // Table of server dialogs ( for loop detection)
  protected ConcurrentHashMap<String, SIPDialog> serverDialogMergeTestTable;

    // A set of methods that result in dialog creations.
    protected static final Set<String> dialogCreatingMethods = new HashSet<String>();

    // Global timer. Use this for all timer tasks.

    private SipTimer timer;

    // List of pending server transactions
    private ConcurrentHashMap<String, SIPServerTransaction> pendingTransactions;

    // hashtable for fast lookup
    private ConcurrentHashMap<String, SIPClientTransaction> clientTransactionTable;

    // Set to false if you want hiwat and lowat to be consulted.
    protected boolean unlimitedServerTransactionTableSize = true;

    // Set to false if you want unlimited size of client trnansactin table.
    protected boolean unlimitedClientTransactionTableSize = true;

    // High water mark for ServerTransaction Table
    // after which requests are dropped.
    protected int serverTransactionTableHighwaterMark = 5000;

    // Low water mark for Server Tx table size after which
    // requests are selectively dropped
    protected int serverTransactionTableLowaterMark = 4000;

    // Hiwater mark for client transaction table. These defaults can be
    // overriden by stack
    // configuration.
    protected int clientTransactionTableHiwaterMark = 1000;

    // Low water mark for client tx table.
    protected int clientTransactionTableLowaterMark = 800;

    private AtomicInteger activeClientTransactionCount = new AtomicInteger(0);

    // Hashtable for server transactions.
    private ConcurrentHashMap<String, SIPServerTransaction> serverTransactionTable;

    // A table of ongoing transactions indexed by mergeId ( for detecting merged
    // requests.
    private ConcurrentHashMap<String, SIPServerTransaction> mergeTable;
   
    private ConcurrentHashMap<String,SIPServerTransaction> terminatedServerTransactionsPendingAck;
   
    private ConcurrentHashMap<String,SIPClientTransaction> forkedClientTransactionTable;
   
    protected boolean deliverRetransmittedAckToListener = false;

    /*
   * A wrapper around differnt logging implementations (log4j, commons
   * logging, slf4j, ...) to help log debug.
     */
    private StackLogger stackLogger;

    /*
     * ServerLog is used just for logging stack message tracecs.
     */
    protected ServerLogger serverLogger;

    /*
     * We support UDP on this stack.
     */
    boolean udpFlag;

    /*
     * Internal router. Use this for all sip: request routing.
     */
    protected DefaultRouter defaultRouter;

    /*
     * Global flag that turns logging off
     */
    protected boolean needsLogging;

    /*
     * Flag used for testing TI, bypasses filtering of ACK to non-2xx
     */
    private boolean non2XXAckPassedToListener;

    /*
     * Class that handles caching of TCP/TLS connections.
     */
    protected IOHandler ioHandler;

    /*
     * Flag that indicates that the stack is active.
     */
    protected boolean toExit;

    /*
     * Name of the stack.
     */
    protected String stackName;

    /*
     * IP address of stack -- this can be re-written by stun.
     *
     * @deprecated
     */
    protected String stackAddress;

    /*
     * INET address of stack (cached to avoid repeated lookup)
     *
     * @deprecated
     */
    protected InetAddress stackInetAddress;

    /*
     * Request factory interface (to be provided by the application)
     */
    protected StackMessageFactory sipMessageFactory;

    /*
     * Router to determine where to forward the request.
     */
    protected javax.sip.address.Router router;

    /*
   * Number of pre-allocated threads for processing udp messages. -1 means no
   * preallocated threads ( dynamically allocated threads).
     */
    protected int threadPoolSize;

    /*
     * max number of simultaneous connections.
     */
    protected int maxConnections;

    /*
     * Close accept socket on completion.
     */
    protected boolean cacheServerConnections;

    /*
     * Close connect socket on Tx termination.
     */
    protected boolean cacheClientConnections;

    /*
     * Use the user supplied router for all out of dialog requests.
     */
    protected boolean useRouterForAll;

    /*
     * Max size of message that can be read from a TCP connection.
     */
    protected int maxContentLength;

    /*
     * Max # of headers that a SIP message can contain.
     */
    protected int maxMessageSize;

    /*
     * A collection of message processors.
     */
    private Collection<MessageProcessor> messageProcessors;

    /*
   * Read timeout on TCP incoming sockets -- defines the time between reads
   * for after delivery of first byte of message.
     */
    protected int readTimeout;

    /*
   * The socket factory. Can be overriden by applications that want direct
   * access to the underlying socket.
     */

    protected NetworkLayer networkLayer;

    /*
   * Outbound proxy String ( to be handed to the outbound proxy class on
   * creation).
     */
    protected String outboundProxy;

    protected String routerPath;

    // Flag to indicate whether the stack will provide dialog
    // support.
    protected boolean isAutomaticDialogSupportEnabled;

    // The set of events for which subscriptions can be forked.

    protected HashSet<String> forkedEvents;

    // Generate a timestamp header for retransmitted requests.
    protected boolean generateTimeStampHeader;

    protected AddressResolver addressResolver;

    // Max time that the listener is allowed to take to respond to a
    // request. Default is "infinity". This property allows
    // containers to defend against buggy clients (that do not
    // want to respond to requests).
    protected int maxListenerResponseTime;

  // A flag that indicates whether or not RFC 2543 clients are fully
  // supported.
    // If this is set to true, then To tag checking on the Dialog layer is
  // disabled in a few places - resulting in possible breakage of forked
  // dialogs.
    protected boolean rfc2543Supported = true;

    // / Provides a mechanism for applications to check the health of threads in
    // the stack
    protected ThreadAuditor threadAuditor = new ThreadAuditor();

    protected LogRecordFactory logRecordFactory;

  // Set to true if the client CANCEL transaction should be checked before
  // sending
    // it out.
    protected boolean cancelClientTransactionChecked = true;

    // Is to tag reassignment allowed.
    protected boolean remoteTagReassignmentAllowed = true;

    protected boolean logStackTraceOnMessageSend = true;
  
    // Receive UDP buffer size
    protected int receiveUdpBufferSize;
   
    // Send UDP buffer size
    protected int sendUdpBufferSize;

    protected boolean stackDoesCongestionControl = true;

    protected boolean isBackToBackUserAgent = false;

    protected boolean checkBranchId;

  protected boolean isAutomaticDialogErrorHandlingEnabled = true;
 
  protected boolean isDialogTerminatedEventDeliveredForNullDialog = false;
 
  // Max time for a forked response to arrive. After this time, the original
  // dialog
  // is not tracked. If you want to track the original transaction you need to
  // specify
  // the max fork time with a stack init property.
  protected int maxForkTime = 0;
 
  // Whether or not to deliver unsolicited NOTIFY

    private boolean deliverUnsolicitedNotify = false;

    private boolean deliverTerminatedEventForAck = false;
 
  // ThreadPool when parsed SIP messages are processed. Affects the case when many TCP calls use single socket.
  private int tcpPostParsingThreadPoolSize = 0;
   
    // Minimum time between NAT kee alive pings from clients.
    // Any ping that exceeds this time will result in  CRLF CRLF going
    // from the UDP message channel.
    protected long minKeepAliveInterval = -1L;

    // The time after which a "dialog timeout event" is delivered to a listener.
  protected int dialogTimeoutFactor = 64;

  // factory used to create MessageParser objects
  public MessageParserFactory messageParserFactory;
  // factory used to create MessageProcessor objects
  public MessageProcessorFactory messageProcessorFactory;
  
  protected boolean aggressiveCleanup = false;
 
  protected static Executor selfRoutingThreadpoolExecutor;
 
  private static class SameThreadExecutor implements Executor {

    public void execute(Runnable command) {
      command.run(); // Just run the command is the same thread
    }
 
  }
 
  public Executor getSelfRoutingThreadpoolExecutor() {
    if(selfRoutingThreadpoolExecutor == null) {
      if(this.threadPoolSize<=0) {
        selfRoutingThreadpoolExecutor = new SameThreadExecutor();
      } else {
        selfRoutingThreadpoolExecutor = Executors.newFixedThreadPool(this.threadPoolSize);
      }
    }
    return selfRoutingThreadpoolExecutor;
  }
    /**
   * Executor used to optimise the ReinviteSender Runnable in the sendRequest
   * of the SipDialog
     */
  private ExecutorService reinviteExecutor = Executors
      .newCachedThreadPool(new ThreadFactory() {
      private int threadCount = 0;

      public Thread newThread(Runnable pRunnable) {
          return new Thread(pRunnable, String.format("%s-%d",
              "ReInviteSender", threadCount++));
      }
    })
 
    // / Timer to regularly ping the thread auditor (on behalf of the timer
    // thread)
    protected class PingTimer extends SIPStackTimerTask {
        // / Timer thread handle
        ThreadAuditor.ThreadHandle threadHandle;

        // / Constructor
        public PingTimer(ThreadAuditor.ThreadHandle a_oThreadHandle) {
            threadHandle = a_oThreadHandle;
        }

        public void runTask() {
            // Check if we still have a timer (it may be null after shutdown)
            if (getTimer() != null) {
                // Register the timer task if we haven't done so
                if (threadHandle == null) {
                    // This happens only once since the thread handle is passed
                    // to the next scheduled ping timer
                    threadHandle = getThreadAuditor().addCurrentThread();
                }

                // Let the thread auditor know that the timer task is alive
                threadHandle.ping();

                // Schedule the next ping
                getTimer().schedule(new PingTimer(threadHandle),
                        threadHandle.getPingIntervalInMillisecs());
            }
        }

    }
   
   
    class RemoveForkedTransactionTimerTask extends SIPStackTimerTask {
       
        private SIPClientTransaction clientTransaction;

    public RemoveForkedTransactionTimerTask(
        SIPClientTransaction sipClientTransaction) {
            this.clientTransaction = sipClientTransaction;
        }

        @Override
        public void runTask() {
      forkedClientTransactionTable.remove(((SIPRequest) clientTransaction
          .getRequest()).getForkId());
        }
       
    }

    static {
      // Standard set of methods that create dialogs.
      dialogCreatingMethods.add(Request.REFER);
        dialogCreatingMethods.add(Request.INVITE);
        dialogCreatingMethods.add(Request.SUBSCRIBE);
    }
   
    /**
     * Default constructor.
     */
    protected SIPTransactionStack() {
        this.toExit = false;
        this.forkedEvents = new HashSet<String>();
        // set of events for which subscriptions can be forked.
        // Set an infinite thread pool size.
        this.threadPoolSize = -1;
        // Close response socket after infinte time.
        // for max performance
        this.cacheServerConnections = true;
        // Close the request socket after infinite time.
        // for max performance
        this.cacheClientConnections = true;
        // Max number of simultaneous connections.
        this.maxConnections = -1;
        // Array of message processors.
        // jeand : using concurrent data structure to avoid excessive blocking
        messageProcessors = new CopyOnWriteArrayList<MessageProcessor>();
        // Handle IO for this process.
        this.ioHandler = new IOHandler(this);

        // The read time out is infinite.
        this.readTimeout = -1;

        this.maxListenerResponseTime = -1;

        // The default (identity) address lookup scheme

        this.addressResolver = new DefaultAddressResolver();

        // Notify may or may not create a dialog. This is handled in
        // the code.
        // Create the transaction collections

        // Dialog dable.
        this.dialogTable = new ConcurrentHashMap<String, SIPDialog>();
        this.earlyDialogTable = new ConcurrentHashMap<String, SIPDialog>();
    this.serverDialogMergeTestTable = new ConcurrentHashMap<String, SIPDialog>();

        clientTransactionTable = new ConcurrentHashMap<String, SIPClientTransaction>();
        serverTransactionTable = new ConcurrentHashMap<String, SIPServerTransaction>();
        this.terminatedServerTransactionsPendingAck = new ConcurrentHashMap<String, SIPServerTransaction>();
        mergeTable = new ConcurrentHashMap<String, SIPServerTransaction>();
        retransmissionAlertTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();

        // Start the timer event thread.

//        this.timer = new DefaultTimer();
        this.pendingTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();
       
       
        this.forkedClientTransactionTable = new ConcurrentHashMap<String,SIPClientTransaction>();       
    }

    /**
     * Re Initialize the stack instance.
     */
    protected void reInit() {
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            stackLogger.logDebug("Re-initializing !");

        // Array of message processors.
        messageProcessors = new CopyOnWriteArrayList<MessageProcessor>();
        // Handle IO for this process.
        this.ioHandler = new IOHandler(this);
        pendingTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();
        clientTransactionTable = new ConcurrentHashMap<String, SIPClientTransaction>();
        serverTransactionTable = new ConcurrentHashMap<String, SIPServerTransaction>();
        retransmissionAlertTransactions = new ConcurrentHashMap<String, SIPServerTransaction>();
        mergeTable = new ConcurrentHashMap<String, SIPServerTransaction>();
        // Dialog dable.
        this.dialogTable = new ConcurrentHashMap<String, SIPDialog>();
        this.earlyDialogTable = new ConcurrentHashMap<String, SIPDialog>();
    this.serverDialogMergeTestTable = new ConcurrentHashMap<String, SIPDialog>();
        this.terminatedServerTransactionsPendingAck = new ConcurrentHashMap<String,SIPServerTransaction>();
        this.forkedClientTransactionTable = new ConcurrentHashMap<String,SIPClientTransaction>();

//        this.timer = new DefaultTimer();

        this.activeClientTransactionCount = new AtomicInteger(0);

    }

    /**
     * Creates and binds, if necessary, a socket connected to the specified
     * destination address and port and then returns its local address.
     *
   * @param dst
   *            the destination address that the socket would need to connect
     *            to.
   * @param dstPort
   *            the port number that the connection would be established with.
   * @param localAddress
   *            the address that we would like to bind on (null for the "any"
   *            address).
   * @param localPort
   *            the port that we'd like our socket to bind to (0 for a random
   *            port).
     *
     * @return the SocketAddress that this handler would use when connecting to
     * the specified destination address and port.
     *
     * @throws IOException
     */
    public SocketAddress obtainLocalAddress(InetAddress dst, int dstPort,
      InetAddress localAddress, int localPort) throws IOException {
    return this.ioHandler.obtainLocalAddress(dst, dstPort, localAddress,
        localPort);

    }

    /**
   * For debugging -- allows you to disable logging or enable logging
   * selectively.
     *
     *
     */
    public void disableLogging() {
        this.getStackLogger().disableLogging();
    }

    /**
     * Globally enable message logging ( for debugging)
     *
     */
    public void enableLogging() {
        this.getStackLogger().enableLogging();
    }

    /**
     * Print the dialog table.
     *
     */
    public void printDialogTable() {
        if (isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      this.getStackLogger().logDebug(
          "dialog table  = " + this.dialogTable);
        }
    }

    /**
   * Retrieve a transaction from our table of transactions with pending
   * retransmission alerts.
     *
     * @param dialogId
   * @return -- the RetransmissionAlert enabled transaction corresponding to
   *         the given dialog ID.
     */
  public SIPServerTransaction getRetransmissionAlertTransaction(
      String dialogId) {
    return (SIPServerTransaction) this.retransmissionAlertTransactions
        .get(dialogId);
    }

    /**
     * Return true if extension is supported.
     *
     * @return true if extension is supported and false otherwise.
     */
    public static boolean isDialogCreated(String method) {
      return dialogCreatingMethods.contains(method);
    }

    /**
     * Add an extension method.
     *
   * @param extensionMethod
   *            -- extension method to support for dialog creation
     */
    public void addExtensionMethod(String extensionMethod) {
        if (extensionMethod.equals(Request.NOTIFY)) {
            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
                stackLogger.logDebug("NOTIFY Supported Natively");
        } else {
            dialogCreatingMethods.add(extensionMethod.trim().toUpperCase());
        }
    }

    /**
     * Put a dialog into the dialog table.
     *
   * @param dialog
   *            -- dialog to put into the dialog table.
     *
     */
    public void putDialog(SIPDialog dialog) {
        String dialogId = dialog.getDialogId();
        if (dialogTable.containsKey(dialogId)) {
            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        stackLogger
            .logDebug("putDialog: dialog already exists" + dialogId
                + " in table = " + dialogTable.get(dialogId));
            }
            return;
        }
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      stackLogger.logDebug("putDialog dialogId=" + dialogId
          + " dialog = " + dialog);
        }
        dialog.setStack(this);
        if (stackLogger.isLoggingEnabled())
            stackLogger.logStackTrace();
        dialogTable.put(dialogId, dialog);
        if (dialog.getMergeId() != null )  {
        this.serverDialogMergeTestTable.put(dialog.getMergeId(), dialog);

    }  
       
    }
   
    /**
     * Create a dialog and add this transaction to it.
     *
   * @param transaction
   *            -- tx to add to the dialog.
     * @return the newly created Dialog.
     */
    public SIPDialog createDialog(SIPTransaction transaction) {

        SIPDialog retval = null;

        if (transaction instanceof SIPClientTransaction) {
      String dialogId = ((SIPRequest) transaction.getRequest())
          .getDialogId(false);
            if (this.earlyDialogTable.get(dialogId) != null) {
                SIPDialog dialog = this.earlyDialogTable.get(dialogId);
        if (dialog.getState() == null
            || dialog.getState() == DialogState.EARLY) {
                    retval = dialog;
                } else {
                    retval = new SIPDialog(transaction);
                    this.earlyDialogTable.put(dialogId, retval);
                }
            } else {
                retval = new SIPDialog(transaction);
                this.earlyDialogTable.put(dialogId, retval);
            }
        } else {
            retval = new SIPDialog(transaction);
        }

        return retval;

    }

    /**
     * Create a Dialog given a client tx and response.
     *
     * @param transaction
     * @param sipResponse
     * @return
     */

  public SIPDialog createDialog(SIPClientTransaction transaction,
      SIPResponse sipResponse) {
    String dialogId = ((SIPRequest) transaction.getRequest())
        .getDialogId(false);
        SIPDialog retval = null;
        if (this.earlyDialogTable.get(dialogId) != null) {
            retval = this.earlyDialogTable.get(dialogId);
            if (sipResponse.isFinalResponse()) {
                this.earlyDialogTable.remove(dialogId);
            }

        } else {
            retval = new SIPDialog(transaction, sipResponse);
        }
        return retval;

    }
    /**
     * Create a Dialog given a sip provider and response.
     *
     * @param sipProvider
     * @param sipResponse
     * @return
     */
    public SIPDialog createDialog(SipProviderImpl sipProvider,
      SIPResponse sipResponse) {
    return new SIPDialog(sipProvider, sipResponse);
  }

    /**
     * Remove the dialog from the dialog table.
     *
   * @param dialog
   *            -- dialog to remove.
     */
    public void removeDialog(SIPDialog dialog) {

        String id = dialog.getDialogId();

        String earlyId = dialog.getEarlyDialogId();

        if (earlyId != null) {
            this.earlyDialogTable.remove(earlyId);
            this.dialogTable.remove(earlyId);
        }

    String mergeId = dialog.getMergeId();

    if (mergeId != null) {
      this.serverDialogMergeTestTable.remove(mergeId);
    }

        if (id != null) {

      // FHT: Remove dialog from table only if its associated dialog is
      // the same as the one
            // specified

            Object old = this.dialogTable.get(id);

            if (old == dialog) {
                this.dialogTable.remove(id);
            }
      
      // We now deliver DTE even when the dialog is not originally present
      // in the Dialog
            // Table
            // This happens before the dialog state is assigned.

            if (!dialog.testAndSetIsDialogTerminatedEventDelivered()) {
        DialogTerminatedEvent event = new DialogTerminatedEvent(dialog
            .getSipProvider(), dialog);

                // Provide notification to the listener that the dialog has
                // ended.
                dialog.getSipProvider().handleEvent(event, null);

            }

        } else if ( this.isDialogTerminatedEventDeliveredForNullDialog ) {
            if (!dialog.testAndSetIsDialogTerminatedEventDelivered()) {
        DialogTerminatedEvent event = new DialogTerminatedEvent(dialog
            .getSipProvider(), dialog);

                // Provide notification to the listener that the dialog has
                // ended.
                dialog.getSipProvider().handleEvent(event, null);

            }
        }

    }

    public SIPDialog getEarlyDialog(String dialogId) {

        SIPDialog sipDialog = (SIPDialog) earlyDialogTable.get(dialogId);
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            stackLogger.logDebug("getEarlyDialog(" + dialogId + ") : returning " + sipDialog);
        }
        return sipDialog;

    }

    /**
   * Return the dialog for a given dialog ID. If compatibility is enabled then
   * we do not assume the presence of tags and hence need to add a flag to
   * indicate whether this is a server or client transaction.
     *
   * @param dialogId
   *            is the dialog id to check.
     */

    public SIPDialog getDialog(String dialogId) {

        SIPDialog sipDialog = (SIPDialog) dialogTable.get(dialogId);
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      stackLogger.logDebug("getDialog(" + dialogId + ") : returning "
          + sipDialog);
    }
        return sipDialog;

    }
   
    /**
   * Remove the dialog given its dialog id. This is used for dialog id
   * re-assignment only.
     *
   * @param dialogId
   *            is the dialog Id to remove.
     */
    public void removeDialog(String dialogId) {
        if (stackLogger.isLoggingEnabled()) {
            stackLogger.logWarning("Silently removing dialog from table");
        }
        dialogTable.remove(dialogId);
    }

    /**
   * Find a matching client SUBSCRIBE to the incoming notify. NOTIFY requests
   * are matched to such SUBSCRIBE requests if they contain the same
   * "Call-ID", a "To" header "tag" parameter which matches the "From" header
   * "tag" parameter of the SUBSCRIBE, and the same "Event" header field.
   * Rules for comparisons of the "Event" headers are described in section
   * 7.2.1. If a matching NOTIFY request contains a "Subscription-State" of
   * "active" or "pending", it creates a new subscription and a new dialog
   * (unless they have already been created by a matching response, as
   * described above).
     *
     * @param notifyMessage
   * @return -- the matching ClientTransaction with semaphore aquired or null
   *         if no such client transaction can be found.
     */
  public SIPClientTransaction findSubscribeTransaction(
      SIPRequest notifyMessage, ListeningPointImpl listeningPoint) {
        SIPClientTransaction retval = null;
        try {
            Iterator it = clientTransactionTable.values().iterator();
            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
        stackLogger.logDebug("ct table size = "
            + clientTransactionTable.size());
            String thisToTag = notifyMessage.getTo().getTag();
            if (thisToTag == null) {
                return retval;
            }
            Event eventHdr = (Event) notifyMessage.getHeader(EventHeader.NAME);
            if (eventHdr == null) {
                if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          stackLogger
              .logDebug("event Header is null -- returning null");
                }

                return retval;
            }
            while (it.hasNext()) {
                SIPClientTransaction ct = (SIPClientTransaction) it.next();
                if (!ct.getMethod().equals(Request.SUBSCRIBE))
                    continue;

                // if ( sipProvider.getListeningPoint(transport) == null)
                String fromTag = ct.getOriginalRequestFromTag();
                Event hisEvent = (Event) ct.getOriginalRequestEvent();
                // Event header is mandatory but some slopply clients
                // dont include it.
                if (hisEvent == null)
                    continue;
                if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                    stackLogger.logDebug("ct.fromTag = " + fromTag);
                    stackLogger.logDebug("thisToTag = " + thisToTag);
                    stackLogger.logDebug("hisEvent = " + hisEvent);
                    stackLogger.logDebug("eventHdr " + eventHdr);
                }

                if fromTag.equalsIgnoreCase(thisToTag)
                      && hisEvent != null
                      && eventHdr.match(hisEvent)
                      && notifyMessage.getCallId().getCallId().equalsIgnoreCase(
                                ct.getOriginalRequestCallId())) {
                    if (!this.isDeliverUnsolicitedNotify() ) {
                        ct.acquireSem();
                    }     
                    retval = ct;
                    return ct;
                }
            }

            return retval;
        } finally {
          if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
        stackLogger.logDebug("findSubscribeTransaction : returning "
            + retval);

        }

    }
   
    /**
     * Add entry to "Transaction Pending ACK" table.
     *
     * @param serverTransaction
     */
    public void addTransactionPendingAck(SIPServerTransaction serverTransaction) {
    String branchId = ((SIPRequest) serverTransaction.getRequest())
        .getTopmostVia().getBranch();
        if ( branchId != null ) {
      this.terminatedServerTransactionsPendingAck.put(branchId,
          serverTransaction);
        }
       
    }
   
    /**
   * Get entry in the server transaction pending ACK table corresponding to an
   * ACK.
     *
     * @param ackMessage
     * @return
     */
    public SIPServerTransaction findTransactionPendingAck(SIPRequest ackMessage) {
    return this.terminatedServerTransactionsPendingAck.get(ackMessage
        .getTopmostVia().getBranch());
    }
   
    /**
     * Remove entry from "Transaction Pending ACK" table.
     *
     * @param serverTransaction
     * @return
     */
   
    public boolean removeTransactionPendingAck(SIPServerTransaction serverTransaction) {
//        String branchId = ((SIPRequest)serverTransaction.getRequest()).getTopmostVia().getBranch();
      String branchId = serverTransaction.getBranchId();
        if ( branchId != null
        && this.terminatedServerTransactionsPendingAck.
                containsKey(branchId) ) {
            this.terminatedServerTransactionsPendingAck.remove(branchId);
            return true;
        } else {
            return false;
        }
    }
   
    /**
     * Check if this entry exists in the "Transaction Pending ACK" table.
     *
     * @param serverTransaction
     * @return
     */
  public boolean isTransactionPendingAck(
      SIPServerTransaction serverTransaction) {
    String branchId = ((SIPRequest) serverTransaction.getRequest())
        .getTopmostVia().getBranch();
        return this.terminatedServerTransactionsPendingAck.contains(branchId);
    }
   
    /**
     * Find the transaction corresponding to a given request.
     *
   * @param sipMessage
   *            request for which to retrieve the transaction.
     *
   * @param isServer
   *            search the server transaction table if true.
     *
   * @return the transaction object corresponding to the request or null if no
   *         such mapping exists.
     */
  public SIPTransaction findTransaction(SIPMessage sipMessage,
      boolean isServer) {
        SIPTransaction retval = null;
        try {
            if (isServer) {
                Via via = sipMessage.getTopmostVia();
                if (via.getBranch() != null) {
                    String key = sipMessage.getTransactionId();

                    retval = (SIPTransaction) serverTransactionTable.get(key);
                    if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            getStackLogger()
                .logDebug(
                    "serverTx: looking for key " + key
                        + " existing="
                                + serverTransactionTable);
          if (key
              .startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
                        return retval;
                    }

                }
                // Need to scan the table for old style transactions (RFC 2543
                // style)
        Iterator<SIPServerTransaction> it = serverTransactionTable
            .values().iterator();
                while (it.hasNext()) {
          SIPServerTransaction sipServerTransaction = (SIPServerTransaction) it
              .next();
          if (sipServerTransaction
              .isMessagePartOfTransaction(sipMessage)) {
                        retval = sipServerTransaction;
                        return retval;
                    }
                }

            } else {
                Via via = sipMessage.getTopmostVia();
                if (via.getBranch() != null) {
                    String key = sipMessage.getTransactionId();
                    if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            getStackLogger().logDebug(
                "clientTx: looking for key " + key);
                    retval = (SIPTransaction) clientTransactionTable.get(key);
          if (key
              .startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
                        return retval;
                    }

                }
                // Need to scan the table for old style transactions (RFC 2543
                // style). This is terribly slow but we need to do this
                // for backasswords compatibility.
        Iterator<SIPClientTransaction> it = clientTransactionTable
            .values().iterator();
                while (it.hasNext()) {
          SIPClientTransaction clientTransaction = (SIPClientTransaction) it
              .next();
          if (clientTransaction
              .isMessagePartOfTransaction(sipMessage)) {
                        retval = clientTransaction;
                        return retval;
                    }
                }

            }
        } finally {
          if ( this.getStackLogger().isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        this.getStackLogger().logDebug(
            "findTransaction: returning  : " + retval);
          }
        }
        return retval;

    }
   
    public SIPTransaction findTransaction(String transactionId, boolean isServer) {
      if(isServer) {
        return serverTransactionTable.get(transactionId);
      } else {
        return clientTransactionTable.get(transactionId);
      }
    }

    /**
   * Get the transaction to cancel. Search the server transaction table for a
   * transaction that matches the given transaction.
     */
  public SIPTransaction findCancelTransaction(SIPRequest cancelRequest,
      boolean isServer) {

        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      stackLogger.logDebug("findCancelTransaction request= \n"
          + cancelRequest + "\nfindCancelRequest isServer="
          + isServer);
        }

        if (isServer) {
      Iterator<SIPServerTransaction> li = this.serverTransactionTable
          .values().iterator();
            while (li.hasNext()) {
                SIPTransaction transaction = (SIPTransaction) li.next();

                SIPServerTransaction sipServerTransaction = (SIPServerTransaction) transaction;
        if (sipServerTransaction
            .doesCancelMatchTransaction(cancelRequest))
                    return sipServerTransaction;
            }

        } else {
      Iterator<SIPClientTransaction> li = this.clientTransactionTable
          .values().iterator();
            while (li.hasNext()) {
                SIPTransaction transaction = (SIPTransaction) li.next();

                SIPClientTransaction sipClientTransaction = (SIPClientTransaction) transaction;
        if (sipClientTransaction
            .doesCancelMatchTransaction(cancelRequest))
                    return sipClientTransaction;

            }

        }
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
      stackLogger
          .logDebug("Could not find transaction for cancel request");
        return null;
    }

    /**
   * Construcor for the stack. Registers the request and response factories
   * for the stack.
     *
   * @param messageFactory
   *            User-implemented factory for processing messages.
     */
    protected SIPTransactionStack(StackMessageFactory messageFactory) {
        this();
        this.sipMessageFactory = messageFactory;
    }

    /**
   * Finds a pending server transaction. Since each request may be handled
   * either statefully or statelessly, we keep a map of pending transactions
   * so that a duplicate transaction is not created if a second request is
   * recieved while the first one is being processed.
     *
     * @param requestReceived
     * @return -- the pending transaction or null if no such transaction exists.
     */
  public SIPServerTransaction findPendingTransaction(
      String transactionId) {
        if (this.stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            this.stackLogger.logDebug("looking for pending tx for :"
                    + transactionId);
        }
    return (SIPServerTransaction) pendingTransactions.get(transactionId);

    }

    /**
   * See if there is a pending transaction with the same Merge ID as the Merge
   * ID obtained from the SIP Request. The Merge table is for handling the
   * following condition: If the request has no tag in the To header field,
   * the UAS core MUST check the request against ongoing transactions. If the
   * From tag, Call-ID, and CSeq exactly match those associated with an
   * ongoing transaction, but the request does not match that transaction
   * (based on the matching rules in Section 17.2.3), the UAS core SHOULD
   * generate a 482 (Loop Detected) response and pass it to the server
   * transaction.
     */
    public boolean findMergedTransaction(SIPRequest sipRequest) {
    if (!sipRequest.getMethod().equals(Request.INVITE)) {
      /*
       * Dont need to worry about request merging for Non-INVITE
       * transactions.
       */
      return false;
    }
    String mergeId = sipRequest.getMergeId();
    if (mergeId != null) {
      SIPServerTransaction mergedTransaction = (SIPServerTransaction) this.mergeTable
          .get(mergeId);
      if (mergedTransaction != null
          && !mergedTransaction
              .isMessagePartOfTransaction(sipRequest)) {
        return true;
      }else {
        /*
         * Check for loop detection for really late arriving
         * requests
         */
        SIPDialog serverDialog = this.serverDialogMergeTestTable
            .get(mergeId);
        if (serverDialog != null && serverDialog.firstTransactionIsServerTransaction
            && serverDialog.getState() == DialogState.CONFIRMED) {
          return true;
        }
      }
    }

    return false;
  }

    /**
   * Remove a pending Server transaction from the stack. This is called after
   * the user code has completed execution in the listener.
     *
   * @param tr
   *            -- pending transaction to remove.
     */
    public void removePendingTransaction(SIPServerTransaction tr) {
        if (this.stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      this.stackLogger.logDebug("removePendingTx: "
          + tr.getTransactionId());
        }
        this.pendingTransactions.remove(tr.getTransactionId());

    }

    /**
     * Remove a transaction from the merge table.
     *
   * @param tr
   *            -- the server transaction to remove from the merge table.
     *
     */
    public void removeFromMergeTable(SIPServerTransaction tr) {
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            this.stackLogger.logDebug("Removing tx from merge table ");
        }
        String key = ((SIPRequest) tr.getRequest()).getMergeId();
        if (key != null) {
            this.mergeTable.remove(key);
        }
    }

    /**
     * Put this into the merge request table.
     *
   * @param sipTransaction
   *            -- transaction to put into the merge table.
     *
     */
  public void putInMergeTable(SIPServerTransaction sipTransaction,
      SIPRequest sipRequest) {
        String mergeKey = sipRequest.getMergeId();
        if (mergeKey != null) {
            this.mergeTable.put(mergeKey, sipTransaction);
        }
    }

    /**
   * Map a Server transaction (possibly sending out a 100 if the server tx is
   * an INVITE). This actually places it in the hash table and makes it known
   * to the stack.
     *
   * @param transaction
   *            -- the server transaction to map.
     */
    public void mapTransaction(SIPServerTransaction transaction) {
        if (transaction.isMapped)
            return;
        addTransactionHash(transaction);
        // transaction.startTransactionTimer();
        transaction.isMapped = true;
    }

    /**
   * Handles a new SIP request. It finds a server transaction to handle this
   * message. If none exists, it creates a new transaction.
     *
   * @param requestReceived
   *            Request to handle.
   * @param requestMessageChannel
   *            Channel that received message.
     *
     * @return A server transaction.
     */
  public ServerRequestInterface newSIPServerRequest(
      SIPRequest requestReceived, MessageChannel requestMessageChannel) {
        // Next transaction in the set
        SIPServerTransaction nextTransaction;
       
        final String key = requestReceived.getTransactionId();

        requestReceived.setMessageChannel(requestMessageChannel);

        // Transaction to handle this request
        SIPServerTransaction currentTransaction = (SIPServerTransaction) serverTransactionTable
        .get(key);

        // Got to do this for bacasswards compatibility.
        if (currentTransaction == null
        || !currentTransaction
            .isMessagePartOfTransaction(requestReceived)) {

            // Loop through all server transactions           
            currentTransaction = null;
      if (!key.toLowerCase().startsWith(
          SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE)) {
            Iterator<SIPServerTransaction> transactionIterator = serverTransactionTable.values().iterator();
        while (transactionIterator.hasNext()
            && currentTransaction == null) {

          nextTransaction = (SIPServerTransaction) transactionIterator
              .next();

                    // If this transaction should handle this request,
          if (nextTransaction
              .isMessagePartOfTransaction(requestReceived)) {
                        // Mark this transaction as the one
                        // to handle this message
                        currentTransaction = nextTransaction;
                    }
                }
            }

            // If no transaction exists to handle this message
            if (currentTransaction == null) {
                currentTransaction = findPendingTransaction(key);
                if (currentTransaction != null) {
                    // Associate the tx with the received request.
                    requestReceived.setTransaction(currentTransaction);
          if (currentTransaction != null
              && currentTransaction.acquireSem())
                        return currentTransaction;
                    else
                        return null;

                }
                // Creating a new server tx. May fail under heavy load.
                currentTransaction = createServerTransaction(requestMessageChannel);
                if (currentTransaction != null) {
                    // currentTransaction.setPassToListener();
                    currentTransaction.setOriginalRequest(requestReceived);
                    // Associate the tx with the received request.
                    requestReceived.setTransaction(currentTransaction);
                }

            }

        }

        // Set ths transaction's encapsulated request
        // interface from the superclass
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      stackLogger.logDebug("newSIPServerRequest( "
          + requestReceived.getMethod() + ":"
          + requestReceived.getTopmostVia().getBranch() + "):"
          + currentTransaction);
        }

        if (currentTransaction != null)
      currentTransaction.setRequestInterface(sipMessageFactory
          .newSIPServerRequest(requestReceived, currentTransaction));

        if (currentTransaction != null && currentTransaction.acquireSem()) {
            return currentTransaction;
        } else if (currentTransaction != null) {
            try {
                /*
         * Already processing a message for this transaction. SEND a
         * trying ( message already being processed ).
                 */
        if (currentTransaction
            .isMessagePartOfTransaction(requestReceived)
            && currentTransaction.getMethod().equals(
                requestReceived.getMethod())) {
          SIPResponse trying = requestReceived
              .createResponse(Response.TRYING);
                    trying.removeContent();
                    currentTransaction.getMessageChannel().sendMessage(trying);
                }
            } catch (Exception ex) {
              if (isLoggingEnabled())
                stackLogger.logError("Exception occured sending TRYING");
            }
            return null;
        } else {
            return null;
        }
    }

    /**
   * Handles a new SIP response. It finds a client transaction to handle this
   * message. If none exists, it sends the message directly to the superclass.
     *
   * @param responseReceived
   *            Response to handle.
   * @param responseMessageChannel
   *            Channel that received message.
     *
     * @return A client transaction.
     */
  public ServerResponseInterface newSIPServerResponse(
      SIPResponse responseReceived, MessageChannel responseMessageChannel) {

        // Iterator through all client transactions
        Iterator<SIPClientTransaction> transactionIterator;
        // Next transaction in the set
        SIPClientTransaction nextTransaction;
        // Transaction to handle this request
        SIPClientTransaction currentTransaction;

        String key = responseReceived.getTransactionId();

        // Note that for RFC 3261 compliant operation, this lookup will
        // return a tx if one exists and hence no need to search through
        // the table.
    currentTransaction = (SIPClientTransaction) clientTransactionTable
        .get(key);

        if (currentTransaction == null
        || (!currentTransaction
            .isMessagePartOfTransaction(responseReceived) && !key
                        .startsWith(SIPConstants.BRANCH_MAGIC_COOKIE_LOWER_CASE))) {
            // Loop through all client transactions

            transactionIterator = clientTransactionTable.values().iterator();
            currentTransaction = null;
            while (transactionIterator.hasNext() && currentTransaction == null) {

        nextTransaction = (SIPClientTransaction) transactionIterator
            .next();

                // If this transaction should handle this request,
        if (nextTransaction
            .isMessagePartOfTransaction(responseReceived)) {

                    // Mark this transaction as the one to
                    // handle this message
                    currentTransaction = nextTransaction;

                }

            }

            // If no transaction exists to handle this message,
            if (currentTransaction == null) {
                // JvB: Need to log before passing the response to the client
                // app, it
                // gets modified!
                if (this.stackLogger.isLoggingEnabled(StackLogger.TRACE_INFO)) {
                    responseMessageChannel.logResponse(responseReceived, System
                            .currentTimeMillis(), "before processing");
                }

                // Pass the message directly to the TU
                return sipMessageFactory.newSIPServerResponse(responseReceived,
                        responseMessageChannel);

            }
        }

        // Aquire the sem -- previous request may still be processing.
        boolean acquired = currentTransaction.acquireSem();
        // Set ths transaction's encapsulated response interface
        // from the superclass
        if (this.stackLogger.isLoggingEnabled(StackLogger.TRACE_INFO)) {
      currentTransaction.logResponse(responseReceived, System
          .currentTimeMillis(), "before processing");
        }

        if (acquired) {
      ServerResponseInterface sri = sipMessageFactory
          .newSIPServerResponse(responseReceived, currentTransaction);
            if (sri != null) {
                currentTransaction.setResponseInterface(sri);
            } else {
                if (this.stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          this.stackLogger
              .logDebug("returning null - serverResponseInterface is null!");
                }
                currentTransaction.releaseSem();
                return null;
            }
        } else {
          if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            this.stackLogger.logDebug("Could not aquire semaphore !!");
        }

        if (acquired)
            return currentTransaction;
        else
            return null;

    }

    /**
   * Creates a client transaction to handle a new request. Gets the real
   * message channel from the superclass, and then creates a new client
   * transaction wrapped around this channel.
     *
   * @param nextHop
   *            Hop to create a channel to contact.
     */
  public MessageChannel createMessageChannel(SIPRequest request,
      MessageProcessor mp, Hop nextHop) throws IOException {
        // New client transaction to return
        SIPTransaction returnChannel;

        // Create a new client transaction around the
        // superclass' message channel
        // Create the host/port of the target hop
        Host targetHost = new Host();
        targetHost.setHostname(nextHop.getHost());
        HostPort targetHostPort = new HostPort();
        targetHostPort.setHost(targetHost);
        targetHostPort.setPort(nextHop.getPort());
        MessageChannel mc = mp.createMessageChannel(targetHostPort);

        // Superclass will return null if no message processor
        // available for the transport.
        if (mc == null)
            return null;

        returnChannel = createClientTransaction(request, mc);

        ((SIPClientTransaction) returnChannel).setViaPort(nextHop.getPort());
        ((SIPClientTransaction) returnChannel).setViaHost(nextHop.getHost());
        addTransactionHash(returnChannel);
        // clientTransactionTable.put(returnChannel.getTransactionId(),
        // returnChannel);
        // Add the transaction timer for the state machine.
        // returnChannel.startTransactionTimer();
        return returnChannel;

    }

    /**
   * Creates a client transaction that encapsulates a MessageChannel. Useful
   * for implementations that want to subclass the standard
     *
   * @param encapsulatedMessageChannel
   *            Message channel of the transport layer.
     */
    public SIPClientTransaction createClientTransaction(SIPRequest sipRequest,
            MessageChannel encapsulatedMessageChannel) {
    SIPClientTransaction ct = new SIPClientTransaction(this,
        encapsulatedMessageChannel);
        ct.setOriginalRequest(sipRequest);
        return ct;
    }

    /**
   * Creates a server transaction that encapsulates a MessageChannel. Useful
   * for implementations that want to subclass the standard
     *
   * @param encapsulatedMessageChannel
   *            Message channel of the transport layer.
     */
  public SIPServerTransaction createServerTransaction(
      MessageChannel encapsulatedMessageChannel) {
    // Issue 256 : be consistent with createClientTransaction, if
    // unlimitedServerTransactionTableSize is true,
      // a new Server Transaction is created no matter what
        if (unlimitedServerTransactionTableSize) {               
            return new SIPServerTransaction(this, encapsulatedMessageChannel);
        } else {
            float threshold = ((float) (serverTransactionTable.size() - serverTransactionTableLowaterMark))
                    / ((float) (serverTransactionTableHighwaterMark - serverTransactionTableLowaterMark));
            boolean decision = Math.random() > 1.0 - threshold;
            if (decision) {
                return null;
            } else {
        return new SIPServerTransaction(this,
            encapsulatedMessageChannel);
            }

        }

    }

    /**
     * Get the size of the client transaction table.
     *
     * @return -- size of the ct table.
     */
    public int getClientTransactionTableSize() {
        return this.clientTransactionTable.size();
    }
   
    /**
     * Get the size of the server transaction table.
     *
     * @return -- size of the server table.
     */
    public int getServerTransactionTableSize() {
        return this.serverTransactionTable.size();
    }

    /**
   * Add a new client transaction to the set of existing transactions. Add it
   * to the top of the list so an incoming response has less work to do in
   * order to find the transaction.
     *
   * @param clientTransaction
   *            -- client transaction to add to the set.
     */
    public void addTransaction(SIPClientTransaction clientTransaction) {
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            stackLogger.logDebug("added transaction " + clientTransaction);
        addTransactionHash(clientTransaction);
      
    }

    /**
   * Remove transaction. This actually gets the tx out of the search
   * structures which the stack keeps around. When the tx
     */
    public void removeTransaction(SIPTransaction sipTransaction) {
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      stackLogger.logDebug("Removing Transaction = "
          + sipTransaction.getTransactionId() + " transaction = "
          + sipTransaction);
        }
        if (sipTransaction instanceof SIPServerTransaction) {
            if (stackLogger.isLoggingEnabled())
                stackLogger.logStackTrace();
            String key = sipTransaction.getTransactionId();
            Object removed = serverTransactionTable.remove(key);
            String method = sipTransaction.getMethod();
      this
          .removePendingTransaction((SIPServerTransaction) sipTransaction);
      this
          .removeTransactionPendingAck((SIPServerTransaction) sipTransaction);
            if (method.equalsIgnoreCase(Request.INVITE)) {
        this
            .removeFromMergeTable((SIPServerTransaction) sipTransaction);
            }
            // Send a notification to the listener.
      SipProviderImpl sipProvider = (SipProviderImpl) sipTransaction
          .getSipProvider();
      if (removed != null
          && sipTransaction.testAndSetTransactionTerminatedEvent()) {
        TransactionTerminatedEvent event = new TransactionTerminatedEvent(
            sipProvider, (ServerTransaction) sipTransaction);

                sipProvider.handleEvent(event, sipTransaction);

            }
        } else {

            String key = sipTransaction.getTransactionId();
            Object removed = clientTransactionTable.remove(key);

            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        stackLogger.logDebug("REMOVED client tx " + removed + " KEY = "
            + key);
                if ( removed != null ) {
                   SIPClientTransaction clientTx = (SIPClientTransaction)removed;
          if (clientTx.getMethod().equals(Request.INVITE)
              && this.maxForkTime != 0) {
            RemoveForkedTransactionTimerTask ttask = new RemoveForkedTransactionTimerTask(
                clientTx);
                       this.timer.schedule(ttask, this.maxForkTime * 1000);
                       clientTx.stopExpiresTimer();
                   }
                }
            }

            // Send a notification to the listener.
      if (removed != null
          && sipTransaction.testAndSetTransactionTerminatedEvent()) {
        SipProviderImpl sipProvider = (SipProviderImpl) sipTransaction
            .getSipProvider();
        TransactionTerminatedEvent event = new TransactionTerminatedEvent(
            sipProvider, (ClientTransaction) sipTransaction);

                sipProvider.handleEvent(event, sipTransaction);
            }

        }
    }

    /**
   * Add a new server transaction to the set of existing transactions. Add it
   * to the top of the list so an incoming ack has less work to do in order to
   * find the transaction.
     *
   * @param serverTransaction
   *            -- server transaction to add to the set.
     */
  public void addTransaction(SIPServerTransaction serverTransaction)
      throws IOException {
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            stackLogger.logDebug("added transaction " + serverTransaction);
        serverTransaction.map();

        addTransactionHash(serverTransaction);
     
    }

    /**
   * Hash table for quick lookup of transactions. Here we wait for room if
   * needed.
     */
    private void addTransactionHash(SIPTransaction sipTransaction) {
        SIPRequest sipRequest = sipTransaction.getOriginalRequest();
        if (sipTransaction instanceof SIPClientTransaction) {
            if (!this.unlimitedClientTransactionTableSize) {
                if (this.activeClientTransactionCount.get() > clientTransactionTableHiwaterMark) {
                    try {
                        synchronized (this.clientTransactionTable) {
                            this.clientTransactionTable.wait();
                            this.activeClientTransactionCount.incrementAndGet();
                        }

                    } catch (Exception ex) {
                        if (stackLogger.isLoggingEnabled()) {
              stackLogger.logError(
                  "Exception occured while waiting for room",
                  ex);
                        }

                    }
                }
            } else {
                this.activeClientTransactionCount.incrementAndGet();
            }
            String key = sipRequest.getTransactionId();
      clientTransactionTable.put(key,
          (SIPClientTransaction) sipTransaction);
           
            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        stackLogger
            .logDebug(" putTransactionHash : " + " key = " + key);
            }
        } else {
            String key = sipRequest.getTransactionId();

            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        stackLogger
            .logDebug(" putTransactionHash : " + " key = " + key);
            }
      serverTransactionTable.put(key,
          (SIPServerTransaction) sipTransaction);

        }

    }

    /**
   * This method is called when a client tx transitions to the Completed or
   * Terminated state.
     *
     */
    protected void decrementActiveClientTransactionCount() {

        if (this.activeClientTransactionCount.decrementAndGet() <= this.clientTransactionTableLowaterMark
                && !this.unlimitedClientTransactionTableSize) {
            synchronized (this.clientTransactionTable) {

                clientTransactionTable.notify();

            }
        }
    }

    /**
     * Remove the transaction from transaction hash.
     */
    protected void removeTransactionHash(SIPTransaction sipTransaction) {
        SIPRequest sipRequest = sipTransaction.getOriginalRequest();
        if (sipRequest == null)
            return;
        if (sipTransaction instanceof SIPClientTransaction) {
            String key = sipTransaction.getTransactionId();
            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                stackLogger.logStackTrace();
                stackLogger.logDebug("removing client Tx : " + key);
            }
            clientTransactionTable.remove(key);

        } else if (sipTransaction instanceof SIPServerTransaction) {
            String key = sipTransaction.getTransactionId();
            serverTransactionTable.remove(key);
            if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
                stackLogger.logDebug("removing server Tx : " + key);
            }
        }
    }

    /**
     * Invoked when an error has ocurred with a transaction.
     *
   * @param transactionErrorEvent
   *            Error event.
     */
  public synchronized void transactionErrorEvent(
      SIPTransactionErrorEvent transactionErrorEvent) {
    SIPTransaction transaction = (SIPTransaction) transactionErrorEvent
        .getSource();

        if (transactionErrorEvent.getErrorID() == SIPTransactionErrorEvent.TRANSPORT_ERROR) {
            // Kill scanning of this transaction.
            transaction.setState(TransactionState._TERMINATED);
            if (transaction instanceof SIPServerTransaction) {
                // let the reaper get him
                ((SIPServerTransaction) transaction).collectionTime = 0;
            }
            transaction.disableTimeoutTimer();
            transaction.disableRetransmissionTimer();
            // Send a IO Exception to the Listener.
        }
    }
   
    /*
     * (non-Javadoc)
   *
   * @see
   * gov.nist.javax.sip.stack.SIPDialogEventListener#dialogErrorEvent(gov.
   * nist.javax.sip.stack.SIPDialogErrorEvent)
     */
  public synchronized void dialogErrorEvent(
      SIPDialogErrorEvent dialogErrorEvent) {
        SIPDialog sipDialog = (SIPDialog) dialogErrorEvent.getSource();
        SipListener sipListener = ((SipStackImpl)this).getSipListener();
    // if the app is not implementing the SipListenerExt interface we delete
    // the dialog to avoid leaks
        if(sipDialog != null && !(sipListener instanceof SipListenerExt)) {
          sipDialog.delete();
        }
    }

    /**
   * Stop stack. Clear all the timer stuff. Make the stack close all accept
   * connections and return. This is useful if you want to start/stop the
   * stack several times from your application. Caution : use of this function
   * could cause peculiar bugs as messages are prcessed asynchronously by the
   * stack.
     */
    public void stopStack() {
        // Prevent NPE on two concurrent stops
        if (this.timer != null)
            this.timer.stop();

        // JvB: set it to null, SIPDialog tries to schedule things after stop
//        timer = null;
        this.pendingTransactions.clear();
        this.toExit = true;
        synchronized (this) {
            this.notifyAll();
        }
        synchronized (this.clientTransactionTable) {
            clientTransactionTable.notifyAll();
        }

        // Threads must periodically check this flag.
        MessageProcessor[] processorList;
        processorList = getMessageProcessors();
        for (int processorIndex = 0; processorIndex < processorList.length; processorIndex++) {
            removeMessageProcessor(processorList[processorIndex]);
        }
        this.ioHandler.closeAll();
        // Let the processing complete.

        try {

            Thread.sleep(1000);

        } catch (InterruptedException ex) {
        }
        this.clientTransactionTable.clear();
        this.serverTransactionTable.clear();

        this.dialogTable.clear();
        this.serverLogger.closeLogFile();

    }

    /**
   * Put a transaction in the pending transaction list. This is to avoid a
   * race condition when a duplicate may arrive when the application is
   * deciding whether to create a transaction or not.
     */
    public void putPendingTransaction(SIPServerTransaction tr) {
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            stackLogger.logDebug("putPendingTransaction: " + tr);

        this.pendingTransactions.put(tr.getTransactionId(), tr);

    }

    /**
   * Return the network layer (i.e. the interface for socket creation or the
   * socket factory for the stack).
     *
     * @return -- the registered Network Layer.
     */
    public NetworkLayer getNetworkLayer() {
        if (networkLayer == null) {
            return DefaultNetworkLayer.SINGLETON;
        } else {
            return networkLayer;
        }
    }

    /**
     * Return true if logging is enabled for this stack.
     *
     * @return true if logging is enabled for this stack instance.
     */
    public boolean isLoggingEnabled() {
    return this.stackLogger == null ? false : this.stackLogger
        .isLoggingEnabled();
    }

    public boolean isLoggingEnabled( int level ) {
    return this.stackLogger == null ? false
        : this.stackLogger.isLoggingEnabled( level );
    }
   
    /**
     * Get the logger.
     *
   * @return --the logger for the sip stack. Each stack has its own logger
   *         instance.
     */
    public StackLogger getStackLogger() {
        return this.stackLogger;
    }

    /**
   * Server log is the place where we log messages for the signaling trace
   * viewer.
     *
   * @return -- the log file where messages are logged for viewing by the
   *         trace viewer.
     */
    public ServerLogger getServerLogger() {
        return this.serverLogger;
    }

    /**
   * Maximum size of a single TCP message. Limiting the size of a single TCP
   * message prevents flooding attacks.
     *
     * @return the size of a single TCP message.
     */
    public int getMaxMessageSize() {
        return this.maxMessageSize;
    }

    /**
   * Set the flag that instructs the stack to only start a single thread for
   * sequentially processing incoming udp messages (thus serializing the
   * processing). Same as setting thread pool size to 1.
     */
    public void setSingleThreaded() {
        this.threadPoolSize = 1;
    }
   
    /**
   * If all calls are occurring on a single TCP socket then the stack would process them in single thread.
   * This property allows immediately after parsing to split the load into many threads which increases
   * the performance significantly. If set to 0 then we just use the old model with single thread.
   *
   * @return
   */
  public int getTcpPostParsingThreadPoolSize() {
    return tcpPostParsingThreadPoolSize;
  }

  /**
   * If all calls are occurring on a single TCP socket then the stack would process them in single thread.
   * This property allows immediately after parsing to split the load into many threads which increases
   * the performance significantly. If set to 0 then we just use the old model with single thread.
   *
   * @param tcpPostParsingThreadPoolSize
   */
  public void setTcpPostParsingThreadPoolSize(int tcpPostParsingThreadPoolSize) {
    this.tcpPostParsingThreadPoolSize = tcpPostParsingThreadPoolSize;
  }

    /**
   * Set the thread pool size for processing incoming UDP messages. Limit the
   * total number of threads for processing udp messages.
     *
   * @param size
   *            -- the thread pool size.
     *
     */
    public void setThreadPoolSize(int size) {
        this.threadPoolSize = size;
    }

    /**
     * Set the max # of simultaneously handled TCP connections.
     *
   * @param nconnections
   *            -- the number of connections to handle.
     */
    public void setMaxConnections(int nconnections) {
        this.maxConnections = nconnections;
    }

    /**
     * Get the default route string.
     *
   * @param sipRequest
   *            is the request for which we want to compute the next hop.
     * @throws SipException
     */
    public Hop getNextHop(SIPRequest sipRequest) throws SipException {
        if (this.useRouterForAll) {
            // Use custom router to route all messages.
            if (router != null)
                return router.getNextHop(sipRequest);
            else
                return null;
        } else {
            // Also non-SIP request containing Route headers goes to the default
            // router
      if (sipRequest.getRequestURI().isSipURI()
          || sipRequest.getRouteHeaders() != null) {
                return defaultRouter.getNextHop(sipRequest);
            } else if (router != null) {
                return router.getNextHop(sipRequest);
            } else
                return null;
        }
    }

    /**
     * Set the descriptive name of the stack.
     *
   * @param stackName
   *            -- descriptive name of the stack.
     */
    public void setStackName(String stackName) {
        this.stackName = stackName;
    }



    /**
     * Set my address.
     *
   * @param stackAddress
   *            -- A string containing the stack address.
     */
  protected void setHostAddress(String stackAddress)
      throws UnknownHostException {
        if (stackAddress.indexOf(':') != stackAddress.lastIndexOf(':')
                && stackAddress.trim().charAt(0) != '[')
            this.stackAddress = '[' + stackAddress + ']';
        else
            this.stackAddress = stackAddress;
        this.stackInetAddress = InetAddress.getByName(stackAddress);
    }

    /**
     * Get my address.
     *
   * @return hostAddress - my host address or null if no host address is
   *         defined.
     * @deprecated
     */
    public String getHostAddress() {

        // JvB: for 1.2 this may return null...
        return this.stackAddress;
    }

    /**
   * Set the router algorithm. This is meant for routing messages out of
   * dialog or for non-sip uri's.
     *
   * @param router
   *            A class that implements the Router interface.
     */
    protected void setRouter(Router router) {
        this.router = router;
    }

    /**
     * Get the router algorithm.
     *
     * @return Router router
     */
    public Router getRouter(SIPRequest request) {
        if (request.getRequestLine() == null) {
            return this.defaultRouter;
        } else if (this.useRouterForAll) {
            return this.router;
        } else {
            if (request.getRequestURI().getScheme().equals("sip")
                    || request.getRequestURI().getScheme().equals("sips")) {
                return this.defaultRouter;
            } else {
                if (this.router != null)
                    return this.router;
                else
                    return defaultRouter;
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see javax.sip.SipStack#getRouter()
     */
    public Router getRouter() {
        return this.router;
    }

    /**
     * return the status of the toExit flag.
     *
     * @return true if the stack object is alive and false otherwise.
     */
    public boolean isAlive() {
        return !toExit;
    }

    /**
   * Adds a new MessageProcessor to the list of running processors for this
   * SIPStack and starts it. You can use this method for dynamic stack
   * configuration.
   */
  protected void addMessageProcessor(MessageProcessor newMessageProcessor)
      throws IOException {
      // Suggested changes by Jeyashankher, jai@lucent.com
      // newMessageProcessor.start() can fail
      // because a local port is not available
      // This throws an IOException.
      // We should not add the message processor to the
      // local list of processors unless the start()
      // call is successful.
      // newMessageProcessor.start();
      messageProcessors.add(newMessageProcessor);


  }

  /**
   * Removes a MessageProcessor from this SIPStack.
   *
   * @param oldMessageProcessor
   */
  protected void removeMessageProcessor(MessageProcessor oldMessageProcessor) {
      if (messageProcessors.remove(oldMessageProcessor)) {
        oldMessageProcessor.stop();
      }

  }

  /**
   * Gets an array of running MessageProcessors on this SIPStack.
   * Acknowledgement: Jeff Keyser suggested that applications should have
   * access to the running message processors and contributed this code.
   *
   * @return an array of running message processors.
   */
  protected MessageProcessor[] getMessageProcessors() {
      return (MessageProcessor[]) messageProcessors
          .toArray(new MessageProcessor[0]);

  }

    /**
   * Creates the equivalent of a JAIN listening point and attaches to the
   * stack.
     *
   * @param ipAddress
   *            -- ip address for the listening point.
   * @param port
   *            -- port for the listening point.
   * @param transport
   *            -- transport for the listening point.
     */
  protected MessageProcessor createMessageProcessor(InetAddress ipAddress,
      int port, String transport) throws java.io.IOException {
    MessageProcessor newMessageProcessor = messageProcessorFactory.createMessageProcessor(this, ipAddress, port, transport);
    this.addMessageProcessor(newMessageProcessor);
    return newMessageProcessor;
  }

    /**
     * Set the message factory.
     *
   * @param messageFactory
   *            -- messageFactory to set.
     */
    protected void setMessageFactory(StackMessageFactory messageFactory) {
        this.sipMessageFactory = messageFactory;
    }

    /**
     * Creates a new MessageChannel for a given Hop.
     *
   * @param sourceIpAddress
   *            - Ip address of the source of this message.
     *
   * @param sourcePort
   *            - source port of the message channel to be created.
     *
   * @param nextHop
   *            Hop to create a MessageChannel to.
     *
   * @return A MessageChannel to the specified Hop, or null if no
   *         MessageProcessors support contacting that Hop.
     *
   * @throws UnknownHostException
   *             If the host in the Hop doesn't exist.
     */
  public MessageChannel createRawMessageChannel(String sourceIpAddress,
      int sourcePort, Hop nextHop) throws UnknownHostException {
        Host targetHost;
        HostPort targetHostPort;
        Iterator processorIterator;
        MessageProcessor nextProcessor;
        MessageChannel newChannel;

        // Create the host/port of the target hop
        targetHost = new Host();
        targetHost.setHostname(nextHop.getHost());
        targetHostPort = new HostPort();
        targetHostPort.setHost(targetHost);
        targetHostPort.setPort(nextHop.getPort());

        // Search each processor for the correct transport
        newChannel = null;
        processorIterator = messageProcessors.iterator();
        while (processorIterator.hasNext() && newChannel == null) {
            nextProcessor = (MessageProcessor) processorIterator.next();
            // If a processor that supports the correct
            // transport is found,
      if (nextHop.getTransport().equalsIgnoreCase(
          nextProcessor.getTransport())
          && sourceIpAddress.equals(nextProcessor.getIpAddress()
              .getHostAddress())
                    && sourcePort == nextProcessor.getPort()) {
                try {
                    // Create a channel to the target
                    // host/port
          newChannel = nextProcessor
              .createMessageChannel(targetHostPort);
                } catch (UnknownHostException ex) {
                    if (stackLogger.isLoggingEnabled())
                        stackLogger.logException(ex);
                    throw ex;
                } catch (IOException e) {
                    if (stackLogger.isLoggingEnabled())
                        stackLogger.logException(e);
                    // Ignore channel creation error -
                    // try next processor
                }
            }
        }
        // Return the newly-created channel
        return newChannel;
    }

    /**
   * Return true if a given event can result in a forked subscription. The
   * stack is configured with a set of event names that can result in forked
   * subscriptions.
     *
   * @param ename
   *            -- event name to check.
     *
     */
    public boolean isEventForked(String ename) {
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            stackLogger.logDebug("isEventForked: " + ename + " returning "
                    + this.forkedEvents.contains(ename));
        }
        return this.forkedEvents.contains(ename);
    }

    /**
     * get the address resolver interface.
     *
     * @return -- the registered address resolver.
     */
    public AddressResolver getAddressResolver() {
        return this.addressResolver;
    }

    /**
     * Set the address resolution interface
     *
   * @param addressResolver
   *            -- the address resolver to set.
     */
    public void setAddressResolver(AddressResolver addressResolver) {
        this.addressResolver = addressResolver;
    }

    /**
     * Set the logger factory.
     *
   * @param logRecordFactory
   *            -- the log record factory to set.
     */
    public void setLogRecordFactory(LogRecordFactory logRecordFactory) {
        this.logRecordFactory = logRecordFactory;
    }

    /**
     * get the thread auditor object
     *
     * @return -- the thread auditor of the stack
     */
    public ThreadAuditor getThreadAuditor() {
        return this.threadAuditor;
    }

    // /
    // / Stack Audit methods
    // /

    /**
     * Audits the SIP Stack for leaks
     *
     * @return Audit report, null if no leaks were found
     */
    public String auditStack(Set activeCallIDs, long leakedDialogTimer,
            long leakedTransactionTimer) {
        String auditReport = null;
        String leakedDialogs = auditDialogs(activeCallIDs, leakedDialogTimer);
    String leakedServerTransactions = auditTransactions(
        serverTransactionTable, leakedTransactionTimer);
    String leakedClientTransactions = auditTransactions(
        clientTransactionTable, leakedTransactionTimer);
        if (leakedDialogs != null || leakedServerTransactions != null
                || leakedClientTransactions != null) {
      auditReport = "SIP Stack Audit:\n"
          + (leakedDialogs != null ? leakedDialogs : "")
          + (leakedServerTransactions != null ? leakedServerTransactions
              : "")
          + (leakedClientTransactions != null ? leakedClientTransactions
              : "");
        }
        return auditReport;
    }

    /**
   * Audits SIP dialogs for leaks - Compares the dialogs in the dialogTable
   * with a list of Call IDs passed by the application. - Dialogs that are not
   * known by the application are leak suspects. - Kill the dialogs that are
   * still around after the timer specified.
     *
     * @return Audit report, null if no dialog leaks were found
     */
    private String auditDialogs(Set activeCallIDs, long leakedDialogTimer) {
        String auditReport = "  Leaked dialogs:\n";
        int leakedDialogs = 0;
        long currentTime = System.currentTimeMillis();

        // Make a shallow copy of the dialog list.
        // This copy will remain intact as leaked dialogs are removed by the
        // stack.
        LinkedList dialogs;
        synchronized (dialogTable) {
            dialogs = new LinkedList(dialogTable.values());
        }

        // Iterate through the dialogDialog, get the callID of each dialog and
        // check if it's in the
        // list of active calls passed by the application. If it isn't, start
        // the timer on it.
        // If the timer has expired, kill the dialog.
        Iterator it = dialogs.iterator();
        while (it.hasNext()) {
            // Get the next dialog
            SIPDialog itDialog = (SIPDialog) it.next();

            // Get the call id associated with this dialog
      CallIdHeader callIdHeader = (itDialog != null ? itDialog
          .getCallId() : null);
      String callID = (callIdHeader != null ? callIdHeader.getCallId()
          : null);

            // Check if the application knows about this call id
      if (itDialog != null && callID != null
          && !activeCallIDs.contains(callID)) {
                // Application doesn't know anything about this dialog...
                if (itDialog.auditTag == 0) {
                    // Mark this dialog as suspect
                    itDialog.auditTag = currentTime;
                } else {
                    // We already audited this dialog before. Check if his
                    // time's up.
                    if (currentTime - itDialog.auditTag >= leakedDialogTimer) {
                        // Leaked dialog found
                        leakedDialogs++;

                        // Generate report
                        DialogState dialogState = itDialog.getState();
            String dialogReport = "dialog id: "
                + itDialog.getDialogId()
                                + ", dialog state: "
                + (dialogState != null ? dialogState.toString()
                    : "null");
                        auditReport += "    " + dialogReport + "\n";

                        // Kill it
                        itDialog.setState(SIPDialog.TERMINATED_STATE);
                        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
              stackLogger.logDebug("auditDialogs: leaked "
                  + dialogReport);
                    }
                }
            }
        }

        // Return final report
        if (leakedDialogs > 0) {
            auditReport += "    Total: " + Integer.toString(leakedDialogs)
                    + " leaked dialogs detected and removed.\n";
        } else {
            auditReport = null;
        }
        return auditReport;
    }

    /**
     * Audits SIP transactions for leaks
     *
     * @return Audit report, null if no transaction leaks were found
     */
    private String auditTransactions(ConcurrentHashMap transactionsMap,
            long a_nLeakedTransactionTimer) {
        String auditReport = "  Leaked transactions:\n";
        int leakedTransactions = 0;
        long currentTime = System.currentTimeMillis();

        // Make a shallow copy of the transaction list.
        // This copy will remain intact as leaked transactions are removed by
        // the stack.
        LinkedList transactionsList = new LinkedList(transactionsMap.values());

        // Iterate through our copy
        Iterator it = transactionsList.iterator();
        while (it.hasNext()) {
            SIPTransaction sipTransaction = (SIPTransaction) it.next();
            if (sipTransaction != null) {
                if (sipTransaction.auditTag == 0) {
                    // First time we see this transaction. Mark it as audited.
                    sipTransaction.auditTag = currentTime;
                } else {
                    // We've seen this transaction before. Check if his time's
                    // up.
                    if (currentTime - sipTransaction.auditTag >= a_nLeakedTransactionTimer) {
                        // Leaked transaction found
                        leakedTransactions++;

                        // Generate some report
            TransactionState transactionState = sipTransaction
                .getState();
            SIPRequest origRequest = sipTransaction
                .getOriginalRequest();
            String origRequestMethod = (origRequest != null ? origRequest
                .getMethod()
                                : null);
            String transactionReport = sipTransaction.getClass()
                .getName()
                                + ", state: "
                + (transactionState != null ? transactionState
                    .toString() : "null")
                + ", OR: "
                + (origRequestMethod != null ? origRequestMethod
                    : "null");
                        auditReport += "    " + transactionReport + "\n";

                        // Kill it
                        removeTransaction(sipTransaction);
                        if (isLoggingEnabled(LogWriter.TRACE_DEBUG))
              stackLogger.logDebug("auditTransactions: leaked "
                  + transactionReport);
                    }
                }
            }
        }

        // Return final report
        if (leakedTransactions > 0) {
            auditReport += "    Total: " + Integer.toString(leakedTransactions)
                    + " leaked transactions detected and removed.\n";
        } else {
            auditReport = null;
        }
        return auditReport;
    }

    public void setNon2XXAckPassedToListener(boolean passToListener) {
        this.non2XXAckPassedToListener = passToListener;
    }

    /**
     * @return the non2XXAckPassedToListener
     */
    public boolean isNon2XXAckPassedToListener() {
        return non2XXAckPassedToListener;
    }

    /**
   * Get the count of client transactions that is not in the completed or
   * terminated state.
     *
     * @return the activeClientTransactionCount
     */
    public int getActiveClientTransactionCount() {
        return activeClientTransactionCount.get();
    }

    public boolean isRfc2543Supported() {

        return this.rfc2543Supported;
    }

    public boolean isCancelClientTransactionChecked() {
        return this.cancelClientTransactionChecked;
    }

    public boolean isRemoteTagReassignmentAllowed() {
        return this.remoteTagReassignmentAllowed;
    }

    /**
     * This method is slated for addition to the next spec revision.
     *
     *
     * @return -- the collection of dialogs that is being managed by the stack.
     */
    public Collection<Dialog> getDialogs() {
        HashSet<Dialog> dialogs = new HashSet<Dialog>();
        dialogs.addAll(this.dialogTable.values());
        dialogs.addAll(this.earlyDialogTable.values());
        return dialogs;
    }

    /**
     *
   * @return -- the collection of dialogs matching the state that is being
   *         managed by the stack.
     */
    public Collection<Dialog> getDialogs(DialogState state) {
        HashSet<Dialog> matchingDialogs = new HashSet<Dialog>();
        if (DialogState.EARLY.equals(state)) {
            matchingDialogs.addAll(this.earlyDialogTable.values());
        } else {
            Collection<SIPDialog> dialogs = dialogTable.values();
            for (SIPDialog dialog : dialogs) {
        if (dialog.getState() != null
            && dialog.getState().equals(state)) {
                    matchingDialogs.add(dialog);
                }
            }
        }
        return matchingDialogs;
    }

    /**
     * Get the Replaced Dialog from the stack.
     *
   * @param replacesHeader
   *            -- the header that references the dialog being replaced.
     */
    public Dialog getReplacesDialog(ReplacesHeader replacesHeader) {
        String cid = replacesHeader.getCallId();
        String fromTag = replacesHeader.getFromTag();
        String toTag = replacesHeader.getToTag();

        StringBuilder dialogId = new StringBuilder(cid);

        // retval.append(COLON).append(to.getUserAtHostPort());
        if (toTag != null) {
            dialogId.append(":");
            dialogId.append(toTag);
        }
        // retval.append(COLON).append(from.getUserAtHostPort());
        if (fromTag != null) {
            dialogId.append(":");
            dialogId.append(fromTag);
        }
        String did = dialogId.toString().toLowerCase();
        if (stackLogger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
          stackLogger.logDebug("Looking for dialog " + did);
        /*
         * Check if we can find this dialog in our dialog table.
         */
        Dialog replacesDialog =  this.dialogTable.get(did);
        /*
         * This could be a forked dialog. Search for it.
         */
        if ( replacesDialog == null ) {
      for (SIPClientTransaction ctx : this.clientTransactionTable
          .values()) {
               if ( ctx.getDialog(did) != null ) {
                   replacesDialog = ctx.getDialog(did);
                   break;
               }
           }
        }

        return replacesDialog;
    }

    /**
     * Get the Join Dialog from the stack.
     *
   * @param joinHeader
   *            -- the header that references the dialog being joined.
     */
    public Dialog getJoinDialog(JoinHeader joinHeader) {
        String cid = joinHeader.getCallId();
        String fromTag = joinHeader.getFromTag();
        String toTag = joinHeader.getToTag();

        StringBuilder retval = new StringBuilder(cid);

        // retval.append(COLON).append(to.getUserAtHostPort());
        if (toTag != null) {
            retval.append(":");
            retval.append(toTag);
        }
        // retval.append(COLON).append(from.getUserAtHostPort());
        if (fromTag != null) {
            retval.append(":");
            retval.append(fromTag);
        }
        return this.dialogTable.get(retval.toString().toLowerCase());
    }

    /**
   * @param timer
   *            the timer to set
     */
    public void setTimer(SipTimer timer) {
        this.timer = timer;
    }

    /**
     * @return the timer
     */
    public SipTimer getTimer() throws IllegalStateException {
//      if(timer == null)
//        throw new IllegalStateException("Stack has been stopped, no further tasks can be scheduled.");
        return timer;
    }

   
    /**
   * Size of the receive UDP buffer. This property affects performance under
   * load. Bigger buffer is better under load.
     *
     * @return
     */
  public int getReceiveUdpBufferSize() {
    return receiveUdpBufferSize;
  }

    /**
   * Size of the receive UDP buffer. This property affects performance under
   * load. Bigger buffer is better under load.
     *
     * @return
     */
  public void setReceiveUdpBufferSize(int receiveUdpBufferSize) {
    this.receiveUdpBufferSize = receiveUdpBufferSize;
  }

    /**
   * Size of the send UDP buffer. This property affects performance under
   * load. Bigger buffer is better under load.
     *
     * @return
     */
  public int getSendUdpBufferSize() {
    return sendUdpBufferSize;
  }

    /**
   * Size of the send UDP buffer. This property affects performance under
   * load. Bigger buffer is better under load.
     *
     * @return
     */
  public void setSendUdpBufferSize(int sendUdpBufferSize) {
    this.sendUdpBufferSize = sendUdpBufferSize;
  }

  /**
   * @param stackLogger
   *            the stackLogger to set
   */
  public void setStackLogger(StackLogger stackLogger) {   
    this.stackLogger = stackLogger;
  }
 
   /**
    * Flag that reqests checking of branch IDs on responses.
    *
    * @return
    */
   public boolean checkBranchId() {
         return this.checkBranchId;
   }

    /**
   * @param logStackTraceOnMessageSend
   *            the logStackTraceOnMessageSend to set
     */
    public void setLogStackTraceOnMessageSend(boolean logStackTraceOnMessageSend) {
        this.logStackTraceOnMessageSend = logStackTraceOnMessageSend;
    }

    /**
     * @return the logStackTraceOnMessageSend
     */
    public boolean isLogStackTraceOnMessageSend() {
        return logStackTraceOnMessageSend;
    }
   
    public void setDeliverDialogTerminatedEventForNullDialog() {
        this.isDialogTerminatedEventDeliveredForNullDialog = true;
    }
   
  public void addForkedClientTransaction(
      SIPClientTransaction clientTransaction) {
    this.forkedClientTransactionTable.put(((SIPRequest) clientTransaction
        .getRequest()).getForkId(), clientTransaction);
    }

    public SIPClientTransaction getForkedTransaction(String transactionId) {
        return this.forkedClientTransactionTable.get(transactionId);
    }

    /**
   * @param deliverUnsolicitedNotify
   *            the deliverUnsolicitedNotify to set
     */
    public void setDeliverUnsolicitedNotify(boolean deliverUnsolicitedNotify) {
        this.deliverUnsolicitedNotify = deliverUnsolicitedNotify;
    }

    /**
     * @return the deliverUnsolicitedNotify
     */
    public boolean isDeliverUnsolicitedNotify() {
        return deliverUnsolicitedNotify;
    }

    /**
   * @param deliverTerminatedEventForAck
   *            the deliverTerminatedEventForAck to set
     */
  public void setDeliverTerminatedEventForAck(
      boolean deliverTerminatedEventForAck) {
        this.deliverTerminatedEventForAck = deliverTerminatedEventForAck;
    }

    /**
     * @return the deliverTerminatedEventForAck
     */
    public boolean isDeliverTerminatedEventForAck() {
        return deliverTerminatedEventForAck;
    }

    public long getMinKeepAliveInterval() {
       return this.minKeepAliveInterval;
    }

    /**
   * @param maxForkTime
   *            the maxForkTime to set
     */
    public void setMaxForkTime(int maxForkTime) {
        this.maxForkTime = maxForkTime;
    }

    /**
     * @return the maxForkTime
     */
    public int getMaxForkTime() {
        return maxForkTime;
    }
   
    /**
   * This is a testing interface. Normally the application does not see
   * retransmitted ACK for 200 OK retransmissions.
     *
     * @return
     */
    public boolean isDeliverRetransmittedAckToListener() {
      return this.deliverRetransmittedAckToListener;
    }

 
    /**
     * Get the dialog timeout counter.
   *
     * @return
     */

  public int getAckTimeoutFactor() {
    if (getSipListener() != null
        && getSipListener() instanceof SipListenerExt) {
      return dialogTimeoutFactor;
    } else {
      return 64;
    }
  }
 
  public abstract SipListener getSipListener();

  /**
   * Executor used to optimize the ReinviteSender Runnable in the sendRequest
   * of the SipDialog
   */
  public ExecutorService getReinviteExecutor() {
    return reinviteExecutor;
  }
 
  /**
   * @param messageParserFactory the messageParserFactory to set
   */
  public void setMessageParserFactory(MessageParserFactory messageParserFactory) {
    this.messageParserFactory = messageParserFactory;
  }

  /**
   * @return the messageParserFactory
   */
  public MessageParserFactory getMessageParserFactory() {
    return messageParserFactory;
  }

  /**
   * @param messageProcessorFactory the messageProcessorFactory to set
   */
  public void setMessageProcessorFactory(MessageProcessorFactory messageProcessorFactory) {
    this.messageProcessorFactory = messageProcessorFactory;
  }

  /**
   * @return the messageProcessorFactory
   */
  public MessageProcessorFactory getMessageProcessorFactory() {
    return messageProcessorFactory;
  }
 
  /**
   * @param aggressiveCleanup the aggressiveCleanup to set
   */
  public void setAggressiveCleanup(boolean aggressiveCleanup) {
      this.aggressiveCleanup = aggressiveCleanup;
  }
 
  /**
   * @return the aggressiveCleanup
   */
  public boolean isAggressiveCleanup() {
      return aggressiveCleanup;
  }

  

    public int getEarlyDialogTimeout() {
        return this.earlyDialogTimeout;
    }
}
TOP

Related Classes of gov.nist.javax.sip.stack.SIPTransactionStack

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.