FileDocCategorySizeDatePackage
SIPTransactionStack.javaAPI DocphoneME MR2 API (J2ME)42163Wed May 02 18:00:42 BST 2007gov.nist.siplite.stack

SIPTransactionStack

public abstract class SIPTransactionStack extends SIPMessageStack implements SIPTransactionEventListener
Adds a transaction layer to the {@link SIPMessageStack} class. This is done by replacing the normal MessageChannels returned by the base class with transaction-aware MessageChannels that encapsulate the original channels and handle the transaction state machine, retransmissions, etc. This code is in the public domain.
version
JAIN-SIP-1.1

Fields Summary
public static final int
BASE_TIMER_INTERVAL
Number of milliseconds between timer ticks (500).
private Vector
clientTransactions
Collection of current client transactions.
private Vector
serverTransactions
Collection or current server transactions.
private Hashtable
dialogTable
Table of dialogs.
protected int
transactionTableSize
Max number of server transactions concurrent.
protected boolean
retransmissionFilter
Retransmission filter - indicates the stack will retransmit 200 OK for invite transactions.
protected Hashtable
dialogCreatingMethods
A set of methods that result in dialog creations.
Constructors Summary
protected SIPTransactionStack()
Default constructor.


       
      
        super();
        this.transactionTableSize = -1;
        // a set of methods that result in dialog creation.
        this.dialogCreatingMethods = new Hashtable();
        // Standard set of methods that create dialogs.
        this.dialogCreatingMethods.put(Request.REFER, "");
        this.dialogCreatingMethods.put(Request.INVITE, "");
        this.dialogCreatingMethods.put(Request.SUBSCRIBE, "");
        // Notify may or may not create a dialog. This is handled in
        // the code.
        // this.dialogCreatingMethods.add(Request.NOTIFY);
        // this.dialogCreatingMethods.put(Request.MESSAGE, "");
        // Create the transaction collections
        clientTransactions = new Vector();
        serverTransactions = new Vector();
        // Dialog dable.
        this.dialogTable = new Hashtable();


        // Start the timer event thread.
        // System.out.println("Starting timeout");
        new Thread(new TransactionScanner()).start();

    
protected SIPTransactionStack(SIPStackMessageFactory messageFactory)
Construcor for the stack. Registers the request and response factories for the stack.

param
messageFactory User-implemented factory for processing messages.

        this();
        super.sipMessageFactory = messageFactory;
    
Methods Summary
public voidaddExtensionMethod(java.lang.String extensionMethod)
Adds an extension method.

param
extensionMethod -- extension method to support for dialog creation

        if (! extensionMethod.equals(Request.NOTIFY)) {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "NOTIFY Supported Natively");
            }
        } else {
            this.dialogCreatingMethods.put(extensionMethod, "");
        }
    
public voidaddTransaction(ClientTransaction clientTransaction)
Adds a new client transaction to the set of existing transactions.

param
clientTransaction -- client transaction to add to the set.

        synchronized (clientTransactions) {
            clientTransactions.addElement(clientTransaction);
        }
    
public voidaddTransaction(ServerTransaction serverTransaction)
Adds a new client transaction to the set of existing transactions.

param
serverTransaction -- server transaction to add to the set.

        synchronized (serverTransactions) {
            this.serverTransactions.addElement(serverTransaction);
        }
    
public booleanallowDialogStateChange(java.lang.String method)
Returns true if method can change dialog state.

param
method the name of the method used for create
return
true if extension is supported and false otherwise.

        return dialogCreatingMethods.containsKey(method.toUpperCase()) ||
            method.equalsIgnoreCase(Request.BYE) ||
            method.equalsIgnoreCase(Request.NOTIFY);
    
public ClientTransactioncreateClientTransaction(MessageChannel encapsulatedMessageChannel)
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.
return
the requested client transaction


        return new ClientTransaction
                (this, encapsulatedMessageChannel);

    
public synchronized DialogcreateDialog(Transaction transaction)
Creates a new dialog for requested transaction.

param
transaction the requested transaction
return
the new Dialog object

        Request sipRequest = transaction.getOriginalRequest();
        Dialog retval = new Dialog(transaction);

        return retval;
    
public MessageChannelcreateMessageChannel(Hop nextHop)
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.
return
the requested message channel

        synchronized (clientTransactions) {
            // New client transaction to return
            Transaction returnChannel;

            // Create a new client transaction around the
            // superclass' message channel
            MessageChannel mc = super.createMessageChannel(nextHop);
            if (mc == null)
                return null;
            returnChannel =
                    createClientTransaction(mc);
            clientTransactions.addElement(returnChannel);
            ((ClientTransaction)returnChannel).setViaPort(nextHop.getPort());
            ((ClientTransaction)returnChannel).setViaHost(nextHop.getHost());
            return returnChannel;
        }
    
public MessageChannelcreateMessageChannel(MessageChannel rawChannel)
Creates a client transaction from a raw channel.

param
rawChannel is the transport channel to encapsulate.
return
the requested message channel

        synchronized (clientTransactions) {
            // New client transaction to return
            Transaction returnChannel =
                    createClientTransaction(rawChannel);
            clientTransactions.addElement(returnChannel);
            ((ClientTransaction)returnChannel).setViaPort
                    (rawChannel.getViaPort());
            ((ClientTransaction)returnChannel).setViaHost
                    (rawChannel.getHost());
            return returnChannel;
        }
    
public MessageChannelcreateMessageChannel(Transaction transaction)
Creates a client transaction from a raw channel.

param
transaction the requested transaction
return
the requested message channel

        synchronized (clientTransactions) {
            // New client transaction to return
            Transaction returnChannel =
                    createClientTransaction(transaction.getMessageChannel());
            clientTransactions.addElement(returnChannel);
            ((ClientTransaction)returnChannel).setViaPort
                    (transaction.getViaPort());
            ((ClientTransaction)returnChannel).setViaHost
                    (transaction.getViaHost());
            return returnChannel;
        }
    
private voidcreateOrTerminateDialog(Request requestReceived)
Creates or terminates a dialog if a subscription matching the given NOTIFY request exists. To create a dialog a list of transactions is searched for the matching transaction and then the corresponding SipClientConnection is notified. To terminate a dialog a list of dialogs is searched for the existing subscription that matches the given NOTIFY request.

param
requestReceived NOTIFY request that may create or terminate a dialog.

        // System.out.println(">>> NOTIFY: Scanning dialogs...");

        synchronized (dialogTable) {
            Enumeration e = dialogTable.elements();

            while (e.hasMoreElements()) {
                Dialog nextDialog = (Dialog)e.nextElement();
                Subscription s = nextDialog.subscriptionList.
                    getMatchingSubscription(requestReceived);

                if (s != null) {
                    SubscriptionStateHeader ssh = (SubscriptionStateHeader)
                        requestReceived.getHeader(Header.SUBSCRIPTION_STATE);

                    if (ssh != null && ssh.isTerminated()) {
                        nextDialog.subscriptionList.removeSubscription(s);
                    } else {
                        nextDialog.setState(Dialog.CONFIRMED_STATE);
                    }

                    return;
                }
            }
        }

        // System.out.println(">>> NOTIFY: Scanning transactions...");

        // Iterator through all client transactions
        Enumeration transactionIterator;
        ClientTransaction currClientTransaction = null;

        // Loop through all client transactions
        synchronized (clientTransactions) {
            transactionIterator = clientTransactions.elements();
            currClientTransaction = null;

            String receivedToTag = requestReceived.getToTag();
            CallIdHeader receivedCid = requestReceived.getCallId();
            EventHeader receivedEvent = (EventHeader)
                requestReceived.getHeader(Header.EVENT);

            while (transactionIterator.hasMoreElements()) {
                currClientTransaction =
                    (ClientTransaction)transactionIterator.nextElement();
                Request request = currClientTransaction.getRequest();
                String method = request.getMethod();
                String fromTag = request.getFromHeaderTag();
                CallIdHeader cid = request.getCallId();
                EventHeader hEvent = (EventHeader)
                    request.getHeader(Header.EVENT);
                boolean isSameEvent = (hEvent != null) &&
                    hEvent.match(receivedEvent);

                if (((method.equals(Request.SUBSCRIBE) && isSameEvent) ||
                        method.equals(Request.REFER)) &&
                    fromTag != null && fromTag.equals(receivedToTag) &&
                        cid != null && cid.equals(receivedCid)) {

                    SipClientConnectionImpl sipClientConnection =
                        (SipClientConnectionImpl)
                            currClientTransaction.getApplicationData();

                    if (sipClientConnection != null) {
                        sipClientConnection.handleMatchingNotify(
                            requestReceived);
                    } else {
                        if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                            Logging.report(Logging.WARNING,
                            LogChannels.LC_JSR180,
                            "SIPTransactionStack.createOrTerminateDialog(): " +
                            "Cannot find SCC for the given NOTIFY.");
                        }
                    }

                    break;
                }
            }
        }
    
public MessageChannelcreateRawMessageChannel(Hop hop)
Creates a raw message channel. A raw message channel has no transaction wrapper.

param
hop hop for which to create the raw message channel.
return
the requested message channel

        return super.createMessageChannel(hop);
    
public ServerTransactioncreateServerTransaction(MessageChannel encapsulatedMessageChannel)
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.
return
the requested server transaction


        return new ServerTransaction
                (this, encapsulatedMessageChannel);
    
public TransactionfindCancelTransaction(Request cancelRequest, boolean isServer)
Gets the transaction to cancel. Search the server transaction table for a transaction that matches the given transaction.

param
cancelRequest the request to be found
param
isServer true if this is a server request
return
the transaction object requested


        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "findCancelTransaction request = \n"
                + cancelRequest +
                "\nfindCancelRequest isServer = " + isServer);
        }

        if (isServer) {
            synchronized (this.serverTransactions) {
                Enumeration li = this.serverTransactions.elements();
                while (li.hasMoreElements()) {
                    Transaction transaction =
                            (Transaction)li.nextElement();
                    Request sipRequest =
                            (Request) (transaction.getRequest());
                    ServerTransaction sipServerTransaction =
                            (ServerTransaction) transaction;
                    if (sipServerTransaction.doesCancelMatchTransaction
                            (cancelRequest))
                        return sipServerTransaction;
                }
            }
        } else {
            synchronized (this.clientTransactions) {
                Enumeration li = this.clientTransactions.elements();
                while (li.hasMoreElements()) {
                    Transaction transaction = (Transaction)li.nextElement();
                    Request sipRequest =
                            (Request) (transaction.getRequest());

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

                }
            }
        }
        return null;
    
public ClientTransactionfindSubscribeTransaction(Request notifyMessage)
Finds a matching client SUBSCRIBE to the incoming notify. NOTIFY requests are matched to such SUBSCRIBE requests if they contain the same "Call-ID", a "ToHeader" header "tag" parameter which matches the "FromHeader" 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 the request to be matched
return
the new client transaction object

        synchronized (clientTransactions) {
            Enumeration it = clientTransactions.elements();
            String thisToHeaderTag = notifyMessage.getTo().getTag();
            if (thisToHeaderTag == null)
                return null;
            EventHeader eventHdr =
                    (EventHeader)notifyMessage.getHeader(Header.EVENT);
            if (eventHdr == null)
                return null;
            while (it.hasMoreElements()) {
                ClientTransaction ct =
                        (ClientTransaction)it.nextElement();
                Request sipRequest = ct.getOriginalRequest();
                String fromTag = sipRequest.getFromHeader().getTag();
                EventHeader hisEvent =
                        (EventHeader)sipRequest.getHeader(Header.EVENT);
                // Event header is mandatory but some slopply clients
                // dont include it.
                if (hisEvent == null) continue;
                if (sipRequest.getMethod().equals(Request.SUBSCRIBE) &&
                        Utils.equalsIgnoreCase(fromTag, thisToHeaderTag) &&
                        hisEvent != null && eventHdr.match(hisEvent) &&
                        Utils.equalsIgnoreCase
                        (notifyMessage.getCallId().getCallId(),
                        sipRequest.getCallId().getCallId()))
                    return ct;
            }

        }
        return null;
    
public TransactionfindTransaction(Message sipMessage, boolean isServer)
Finds 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.


        if (isServer) {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "searching server transaction for "
                    + sipMessage + " size = " +
                    this.serverTransactions.size());
            }

            synchronized (this.serverTransactions) {
                Enumeration it = serverTransactions.elements();
                while (it.hasMoreElements()) {
                    ServerTransaction sipServerTransaction =
                            (ServerTransaction)it.nextElement();
                    if (sipServerTransaction
                            .isMessagePartOfTransaction(sipMessage))
                        return sipServerTransaction;
                }
            }
        } else {
            synchronized (this.clientTransactions) {
                Enumeration it = clientTransactions.elements();
                while (it.hasMoreElements()) {
                    ClientTransaction clientTransaction =
                            (ClientTransaction)it.nextElement();
                    if (clientTransaction
                            .isMessagePartOfTransaction(sipMessage))
                        return clientTransaction;
                }
            }

        }
        return null;


    
public DialoggetDialog(java.lang.String dialogId)
Returns 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.
return
the Dialog object for the requested id

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "Getting dialog for " + dialogId);
        }

        synchronized (dialogTable) {
            return (Dialog)dialogTable.get(dialogId);
        }
    
public booleanisDialogCreated(java.lang.String method)
Returns true if extension is supported.

param
method the name of the method used for create
return
true if extension is supported and false otherwise.

        // printDialogCreatingMethods();
        // System.out.println("CHECKING IF DIALOG HAS BEEN CREATED"
        // + " FOR THE FOLLOWING METHOD"
        // + method.toUpperCase());
        return dialogCreatingMethods.containsKey(method.toUpperCase());
    
protected SIPServerRequestInterfacenewSIPServerRequest(Request requestReceived, MessageChannel requestMessageChannel)
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.


        try {
            // Iterator through all server transactions
            Enumeration transactionIterator;
            // Next transaction in the set
            ServerTransaction nextTransaction;
            // Transaction to handle this request
            ServerTransaction currentTransaction = null;

            if (requestReceived.getMethod().equals(Request.NOTIFY)) {
                createOrTerminateDialog(requestReceived);
            }

            // Loop through all server transactions
            synchronized (serverTransactions) {
                transactionIterator = serverTransactions.elements();
                currentTransaction = null;
                while (transactionIterator.hasMoreElements() &&
                        currentTransaction == null) {

                    nextTransaction =
                        (ServerTransaction)transactionIterator.nextElement();

                    // If this transaction should handle this request,
                    if (!nextTransaction.isTerminated() &&
                            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 =
                            createServerTransaction(requestMessageChannel);
                    currentTransaction.setOriginalRequest(requestReceived);
                    if (!isDialogCreated(requestReceived.getMethod())) {
                        /*
                         * IMPL_NOTE: remove the following block of code
                         *       after ensuring that it doesn't break anything.
                         */

                        /*
                        // Dialog is not created - can we find the state?
                        // If so, then create a transaction and add it.
                        String dialogId = requestReceived.getDialogId(true);
                        Dialog dialog = getDialog(dialogId);
                        // Sequence numbers are supposed to increment.
                        // avoid processing old sequence numbers and
                        // delivering the same request up to the
                        // application if the request has already been seen.
                        // Special handling applies to ACK processing.
                        if (dialog != null &&
                            (requestReceived.getMethod().equals(Request.ACK)
                                || requestReceived.getCSeqHeader()
                                    .getSequenceNumber() >
                                        dialog.getRemoteSequenceNumber())) {
                            // Found a dialog.
                            if (Logging.REPORT_LEVEL <=
                                    Logging.INFORMATION) {
                                Logging.report(Logging.INFORMATION,
                                    LogChannels.LC_JSR180,
                                    "adding server transaction " +
                                    currentTransaction);
                            }

                            serverTransactions.addElement(currentTransaction);
                            currentTransaction.isMapped = true;
                        }
                        */

                        // Server transactions must always be added
                        // to the corresponding vector.
                        serverTransactions.addElement(currentTransaction);
                        currentTransaction.isMapped = true;
                    } else {
                        // Create the transaction but dont map it.
                        String dialogId = requestReceived.getDialogId(true);
                        Dialog dialog = getDialog(dialogId);
                        // This is a dialog creating request that is
                        // part of an existing dialog
                        // (eg. re-Invite). Re-invites get a non null
                        // server transaction Id (unlike the original
                        // invite).
                        if (dialog != null &&
                            requestReceived.getCSeqHeader().getSequenceNumber()
                                > dialog.getRemoteSequenceNumber()) {
                            try {
                                currentTransaction.map();
                            } catch (IOException ex) {
                                /* Ignore */
                            }
                        }

                        serverTransactions.addElement(currentTransaction);
                        currentTransaction.toListener = true;
                    }
                }
                // attach to request
                requestReceived.setTransaction(currentTransaction);

                // Set ths transaction's encapsulated request
                // interface from the superclass
                currentTransaction.setRequestInterface
                        (super.newSIPServerRequest
                        (requestReceived, currentTransaction));

                return currentTransaction;
            }
        } catch (RuntimeException ex) {
            ex.printStackTrace();
            throw ex;
        }

    
SIPServerResponseInterfacenewSIPServerResponse(Response responseReceived, MessageChannel responseMessageChannel)
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.

        // System.out.println("response = " + responseReceived.encode());
        // Iterator through all client transactions
        Enumeration transactionIterator;
        // Next transaction in the set
        ClientTransaction nextTransaction;
        // Transaction to handle this request
        ClientTransaction currentTransaction;

        // Loop through all client transactions
        synchronized (clientTransactions) {
            transactionIterator = clientTransactions.elements();
            currentTransaction = null;
            int i = -1;
            while (transactionIterator.hasMoreElements() &&
                    currentTransaction == null) {
                i++;
                nextTransaction =
                        (ClientTransaction)transactionIterator.nextElement();
                // If this transaction should handle this request,
                if (nextTransaction.isMessageTransOrMult(responseReceived)) {
                    if (nextTransaction.isMultipleResponse(responseReceived)) {
                        // RFC 3261, 13.2.2.4:
                        // Multiple 2xx responses may arrive at the UAC for
                        // a single INVITE request due to a forking proxy.
                        // create a new client transaction
                        currentTransaction =
                            nextTransaction.cloneWithNewLastResponse
                                (responseReceived);
                        currentTransaction.setState
                            (Transaction.PROCEEDING_STATE);
                        currentTransaction.setApplicationData
                            (nextTransaction.getApplicationData());
                        Dialog dialog = new Dialog(currentTransaction);
                        dialog.setDialogId(responseReceived.getDialogId(false));
                        dialog.setRemoteTag(responseReceived.getToTag());
                        dialog.setStack(this);
                        putDialog(dialog);
                        currentTransaction.setDialog(dialog);
                        // change from old to new transaction
                        clientTransactions.setElementAt(currentTransaction, i);
                    } else { // not multiple response
                        // Mark this transaction as the one to
                        // handle this message
                        currentTransaction = nextTransaction;
                    }

                }
            }
        }
        // If no transaction exists to handle this message,
        if (currentTransaction == null) {
            // Pass the message directly to the TU
            return super.newSIPServerResponse
                    (responseReceived, responseMessageChannel);
        }
        // Set ths transaction's encapsulated response interface
        // from the superclass
        currentTransaction.setResponseInterface(super.newSIPServerResponse
                (responseReceived,
                currentTransaction));
        return currentTransaction;
    
private voidprintDialogCreatingMethods()
Prints the Dialog creating methods.

        System.out.println("PRINTING DIALOGCREATINGMETHODS HASHTABLE");
        Enumeration e = dialogCreatingMethods.keys();
        while (e.hasMoreElements()) {
            System.out.println(e.nextElement());
        }
        System.out.println("DIALOGCREATINGMETHODS HASHTABLE PRINTED");
    
public voidputDialog(Dialog dialog)
Puts a dialog into the dialog table.

param
dialog -- dialog to put into the dialog table.

        String dialogId = dialog.getDialogId();

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "putDialog dialogId=" + dialogId);
        }

        // if (this.getDefaultRouteHeader() != null)
        // dialog.addRoute(this.getDefaultRouteHeader(), false);
        dialog.setStack(this);

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            // new Exception().printStackTrace();
        }

        synchronized (dialogTable) {
            dialogTable.put(dialogId, dialog);
        }