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

SipProvider.java

/*
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package gov.nist.siplite;

import java.util.*;
import java.io.*;
import javax.microedition.sip.SipException;

import gov.nist.siplite.header.*;
import gov.nist.siplite.message.*;
import gov.nist.siplite.stack.*;
import gov.nist.core.*;
import gov.nist.siplite.address.*;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * Implementation of the JAIN-SIP provider interface.
 *
 * @version  JAIN-SIP-1.1
 *
 * <a href="{@docRoot}/uncopyright.html">
 * This code is in the public domain.</a>
 *
 */
public final class SipProvider implements
        SIPTransactionEventListener {
    /** Current SIP listener. */
    protected   SipListener sipListener;
    /** Flag to indicate the provider is active. */
    protected boolean isActive;
    /** Current SIP Stack context. */
    protected  SipStack sipStack;
    /** Current event listening filter. */
    protected  ListeningPoint listeningPoint;
    /** Curent server transactions. */
    protected   ServerTransaction currentTransaction;
    /** Curent event scanner processor. */
    private    EventScanner eventScanner;

    /**
     * Stops processing messages for this provider. Post an empty
     * message to our message processing queue that signals us to
     * quit.
     */
    protected  void stop() {
        // Put an empty event in the queue and post ourselves a message.
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "Exiting provider");
        }

        synchronized (this) {
            listeningPoint.removeSipProvider();
        }

        this.eventScanner.stop();
    }


    /**
     * Handles the SIP event - because we have only one listener and we are
     * already in the context of a separate thread, we dont need to enque
     * the event and signal another thread.
     *
     * @param sipEvent is the event to process.
     * @param transaction the current transaction
     *
     */
    public void handleEvent(SipEvent sipEvent,
            Transaction transaction) {

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "handleEvent " + sipEvent +
                "currentTransaction = " + transaction +
                "this.sipListener = " + this.sipListener);
        }

        if (this.sipListener == null)
            return;

        EventWrapper eventWrapper = new EventWrapper();
        eventWrapper.sipEvent = sipEvent;
        eventWrapper.transaction = transaction;

        if (transaction != null &&
            transaction instanceof ClientTransaction) {
                ((ClientTransaction)transaction).setEventPending();
        }

        this.eventScanner.addEvent(eventWrapper);
    }

    /**
     * Creates a new instance of SipProvider.
     * @param sipStack the current SIP stack context
     */
    protected SipProvider(SipStack sipStack) {
        this.sipStack = sipStack;
        this.eventScanner = sipStack.eventScanner;
    }

    /**
     * Compares to this instance for equivalence.
     * @param obj object for comparison
     * @return true if the object is the same
     */
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    /**
     * This method registers the SipListener object to this SipProvider, once
     * registered the SIP Listener can send events on the SipProvider and
     * recieve events emitted from the SipProvider. As JAIN SIP resticts a
     * unicast Listener special case, that is, that one and only one Listener
     * may be registered on the SipProvider concurrently.
     * <p>
     * If an attempt is made to re-register the existing SipListener this
     * method returns silently. A previous SipListener must be removed from the
     * SipProvider before another SipListener can be registered to
     * the SipProvider.
     *
     * @param sipListener to be registered with the
     * Provider.
     * @throws TooManyListenersException this exception is thrown when a new
     * SipListener attempts to register with the SipProvider when another
     * SipListener is already registered with this SipProvider.
     *
     */
    public void addSipListener(SipListener sipListener)
    throws TooManyListenersException {

        synchronized (sipStack) {
            Enumeration it = sipStack.getSipProviders();
            while (it.hasMoreElements()) {
                SipProvider provider = (SipProvider) it.nextElement();
                if (provider.sipListener  != null &&
                        provider.sipListener != sipListener)
                    throw new TooManyListenersException();
            }
        }

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "add SipListener " + sipListener);
        }

        this.sipListener = sipListener;
        sipStack.sipListener = sipListener;

        synchronized (sipStack) {
            Enumeration it = sipStack.getSipProviders();
            while (it.hasMoreElements()) {
                SipProvider provider = (SipProvider) it.nextElement();
                provider.sipListener = sipListener;
            }
        }
    }

    /**
     * Returns the ListeningPoint of this SipProvider.
     * A SipProvider has a single Listening Point at any specific point in time.
     *
     * @see ListeningPoint
     * @return the ListeningPoint of this SipProvider
     */
    public ListeningPoint getListeningPoint() {
        return this.listeningPoint;
    }

    /**
     * Returns a unique CallIdHeader for identifying dialogues between two
     * SIP applications.
     *
     * @return new CallId unique within the SIP Stack.
     */
    public CallIdHeader getNewCallId() {
        String callId = SIPUtils.generateCallIdentifier
                (this.getSipStack().getIPAddress());
        CallIdHeader callid = new  CallIdHeader();
        callid.setCallId(callId);
        return callid;

    }

    /**
     * Once an application wants to a send a new request it must first request
     * a new client transaction identifier. This method is called by an
     * application to create the client transaction befores it sends the Request
     * via the SipProvider on that transaction. This methods returns a new
     * unique client transaction identifier that can be passed to the stateful
     * sendRequest method on the SipProvider and the sendAck/sendBye
     * methods on the Dialog in order to send a request.
     *
     * @param request the new Request message that is to handled
     * statefully by the Provider.
     * @return a new unique client transation identifier
     * @see ClientTransaction
     * @since v1.1
     */
    public ClientTransaction getNewClientTransaction(Request request)
            throws TransactionUnavailableException {
        if (request == null) {
            throw new NullPointerException("null request");
        }

        if (request.getTransaction() != null) {
            throw new TransactionUnavailableException
                    ("Transaction already assigned to request");
        }

        if (request.getMethod().equals(Request.CANCEL)) {
            ClientTransaction ct = (ClientTransaction)
                sipStack.findCancelTransaction(request, false);

            if (ct != null) {
                ClientTransaction retval = sipStack.createClientTransaction
                        (ct.getMessageChannel());
                ((Transaction)retval).setOriginalRequest(request);
                ((Transaction)retval).addEventListener(this);
                sipStack.addTransaction((ClientTransaction)retval);
                ((ClientTransaction)retval).setDialog((Dialog)ct.getDialog());
                return retval;
            }

        }

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "could not find existing transaction for "
                + request.getFirstLine());
        }

        String dialogId = request.getDialogId(false);
        Dialog dialog = sipStack.getDialog(dialogId);
        // isDialog - is request sends inside dialog
        boolean isDialog = false;
        if (dialog != null) {
            int dialogState = dialog.getState();
            if (dialogState == Dialog.EARLY_STATE ||
                dialogState == Dialog.CONFIRMED_STATE ||
                dialogState == Dialog.COMPLETED_STATE) {
                isDialog = true;
            }
        }
        Enumeration it = sipStack.getRouter().getNextHops(request, isDialog);

        if (it == null || !it.hasMoreElements())  {
            // could not route the request as out of dialog.
            // maybe the user has no router or the router cannot resolve
            // the route.
            // If this is part of a dialog then use the route from the dialog
            if (dialog != null)   {
                try {
                    Hop hop = dialog.getNextHop();

                    if (hop != null) {
                        ClientTransaction ct = null;

                        ct = (ClientTransaction) sipStack
                            .createMessageChannel(hop);
                        
                        String branchId = SIPUtils.generateBranchId();

                        if (request.getTopmostVia() != null) {
                            request.getTopmostVia().setBranch(branchId);
                        } else {
                            // Find a message processor to assign this
                            // transaction to.
                            MessageProcessor messageProcessor =
                                    this.listeningPoint.messageProcessor;
                            ViaHeader via = messageProcessor.getViaHeader();
                            request.addHeader(via);
                        }

                        ct.setOriginalRequest(request);
                        ct.setBranch(branchId);
                        ct.setDialog(dialog);
                        ct.addEventListener(this);

                        return ct;
                    } // end if
                } catch (Exception ex) {
                    throw new TransactionUnavailableException(ex.getMessage());
                }
            } else {
                throw new TransactionUnavailableException("no route!");
            }
        } else {
            try {
                // An out of dialog route was found. Assign this to the
                // client transaction.
                while (it.hasMoreElements()) {
                    Hop hop = (Hop) it.nextElement();

                    ClientTransaction ct = null;

                    ct = (ClientTransaction) sipStack
                        .createMessageChannel(hop);

                    // ClientTransaction ct =
                    //   (ClientTransaction) sipStack.createMessageChannel(hop);

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

                    if (ct == null) continue;

                    String branchId = SIPUtils.generateBranchId();

                    if (request.getTopmostVia() != null) {
                        request.getTopmostVia().setBranch(branchId);
                    } else {
                        // Find a message processor to assign
                        // this transaction to.
                        MessageProcessor messageProcessor =
                                listeningPoint.messageProcessor;
                        ViaHeader via = messageProcessor.getViaHeader();
                        request.addHeader(via);
                    }

                    ct.setOriginalRequest(request);
                    ct.setBranch(branchId);

                    if (sipStack.isDialogCreated(request.getMethod())) {
                        // create a new dialog to contain this transaction
                        // provided this is necessary.
                        // This could be a re-invite
                        // (noticed by Brad Templeton)

                        if (dialog != null) {
                            ct.setDialog(dialog);
                        } else {
                            sipStack.createDialog(ct);
                        }
                    } else {
                        ct.setDialog(dialog);
                    }

                    // The provider is the event listener for all transactions.
                    ct.addEventListener(this);
                    return (ClientTransaction) ct;
                } // end while()
            } catch (SipException ex) {
                throw new TransactionUnavailableException(ex.getMessage());
            }
        } // end else

        throw new TransactionUnavailableException
                ("Could not create transaction - could not resolve next hop! ");
    }

    /**
     * An application has the responsibility of deciding to respond to a
     * Request that does not match an existing server transaction. The method
     * is called by an application that decides to respond to an unmatched
     * Request statefully. This methods return a new unique server transaction
     * identifier that can be passed to the stateful sendResponse methods in
     * order to respond to the request.
     *
     * @param request the initial Request message that the doesn't
     * match an existing
     * transaction that the application decides to handle statefully.
     * @return a new unique server transation identifier
     * @throws TransactionAlreadyExistsException if a transaction already exists
     * that is already handling this Request. This may happen if the application
     * gets retransmits of the same request before the initial transaction is
     * allocated.
     * @see ServerTransaction
     * @since v1.1
     */
    public ServerTransaction
            getNewServerTransaction(Request request)
            throws TransactionAlreadyExistsException,
            TransactionUnavailableException {

        try {
            ServerTransaction transaction = null;
            Request sipRequest = (Request) request;
            if (sipStack.isDialogCreated(sipRequest.getMethod())) {
                if (sipStack.findTransaction((Request)request, true) != null)
                    throw new TransactionAlreadyExistsException
                            ("server transaction already exists!");
                transaction = (ServerTransaction) this.currentTransaction;
                if (transaction == null)
                    throw new  TransactionUnavailableException
                            ("Transaction not available");
                if (!transaction
                        .isMessagePartOfTransaction((Request) request)) {
                    throw new TransactionUnavailableException
                            ("Request Mismatch");
                }
                transaction.setOriginalRequest(sipRequest);
                try {
                    sipStack.addTransaction(transaction);
                } catch (IOException ex) {
                    throw new TransactionUnavailableException
                            ("Error sending provisional response");
                }
                equipADialogForTransaction(transaction, sipRequest);
            } else {
                transaction = (ServerTransaction)
                sipStack.findTransaction((Request) request, true);
                if (transaction != null)
                    throw new TransactionAlreadyExistsException
                            ("Transaction exists! ");
                transaction = (ServerTransaction) this.currentTransaction;
                if (transaction == null)
                    throw new TransactionUnavailableException
                            ("Transaction not available!");
                if (!transaction
                        .isMessagePartOfTransaction((Request) request))
                    throw new TransactionUnavailableException
                            ("Request Mismatch");
                transaction.setOriginalRequest(sipRequest);
                // Map the transaction.
                try {
                    sipStack.addTransaction(transaction);
                } catch (IOException ex) {
                    throw new TransactionUnavailableException
                            ("Could not send back provisional response!");
                }
                String dialogId =   sipRequest.getDialogId(true);
                Dialog dialog = sipStack.getDialog(dialogId);
                if (dialog != null) {
                    dialog.addTransaction(transaction);
                    dialog.addRoute(sipRequest);
                }
            }
            return transaction;
        } catch (RuntimeException ex) {
            ex.printStackTrace();
            throw ex;
        }
    }

    /**
     * Find or create a dialog with the dialog-id obtained from the request.
     * Initializing the dialog's route information.
     * Bind the dialog and the transaction to each other.
     * @param transaction trasaction for the request
     * @param sipRequest request whose tags are the source of dialog-ID
     *                   and route information
     */
    public void equipADialogForTransaction(
            ServerTransaction transaction,
            Request sipRequest) {
        // So I can handle timeouts.
        // IMPL_NOTE: do we need it here, or in the calling code?
        transaction.addEventListener(this);

        String dialogId = sipRequest.getDialogId(true);
        Dialog dialog = sipStack.getDialog(dialogId);

        if (dialog == null) {
            dialog = sipStack.createDialog(transaction);
        }

        dialog.setStack(this.sipStack);
        dialog.addRoute(sipRequest);

        if (dialog.getRemoteTag() != null &&
                dialog.getLocalTag() != null)  {
            this.sipStack.putDialog(dialog);
        }

        transaction.setDialog(dialog);
    }

    /**
     * Returns the SipStack that this SipProvider is attached to. A SipProvider
     * can only be attached to a single SipStack object which belongs to
     * the same SIP stack as the SipProvider.
     *
     * @see SipStack
     * @return the attached SipStack.
     */
    public SipStack getSipStack() {
        return  this.sipStack;
    }

    /**
     * Removes the SipListener from this SipProvider. This method returns
     * silently if the <var>sipListener</var> argument is not registered
     * with the SipProvider.
     *
     * @param sipListener - the SipListener to be removed from this
     * SipProvider
     */
    public void removeSipListener(SipListener sipListener) {
        if (sipListener == this.sipListener) {
            this.sipListener = null;
        }
    }

    /**
     * Sends specified Request and returns void i.e.
     * no transaction record is associated with this action. This method
     * implies that the application is functioning statelessly specific to this
     * Request, hence the underlying SipProvider acts statelessly.
     * <p>
     * Once the Request message has been passed to this method, the SipProvider
     * will forget about this Request. No transaction semantics will be
     * associated with the Request and no retranmissions will occur on the
     * Request by the SipProvider, if these semantics are required it is the
     * responsibility of the application not the JAIN SIP Stack.
     * <ul>
     * <li>Stateless Proxy - A stateless proxy simply forwards every request
     *  it receives downstream and discards information about the request
     *  message once the message has been forwarded. A stateless proxy does not
     *  have any notion of a transaction.
     * </ul>
     *
     * @since v1.1
     * @see Request
     * @param request - the Request message to send statelessly
     * @throws SipException if implementation cannot send request for any reason
     */
    public void sendRequest(Request request) throws SipException {
        // request sends out of dialog
        Enumeration it = sipStack.getRouter().getNextHops(request, false);
        if (it == null || !it.hasMoreElements()) {
            throw new SipException("could not determine next hop!",
                SipException.GENERAL_ERROR);
        }

        // Will slow down the implementation because it involves
        // a search to see if a transaction exists.
        // Just to double check adding some assertion
        // checking under debug.
        Transaction tr = sipStack.findTransaction(request, false);
        if (tr != null) {
            throw new SipException("Cannot send: stateless Transaction found!",
                SipException.GENERAL_ERROR);
        }

        while (it.hasMoreElements()) {
            Hop nextHop = (Hop) it.nextElement();

            Request sipRequest = request;
            String bid = sipRequest.getTransactionId();
            ViaHeader via = sipRequest.getTopmostVia();
            via.setBranch(bid);
            Request newRequest;

            // Do not create a transaction for this request. If it has
            // Mutliple route headers then take the first one off the
            // list and copy into the request URI.
            if (sipRequest.getHeader(Header.ROUTE) != null) {
                newRequest = (Request) sipRequest.clone();
                Enumeration rl =
                        newRequest.getHeaders(Header.ROUTE);
                RouteHeader route = (RouteHeader) rl.nextElement();
                newRequest.setRequestURI(route.getAddress().getURI());
                sipRequest.removeHeader(Header.ROUTE, true);
            } else {
                newRequest = sipRequest;
            }

            MessageChannel messageChannel =
                    sipStack.createRawMessageChannel(nextHop);
            try {
                if (messageChannel != null) {
                    messageChannel.sendMessage((Message)newRequest);
                    return;
                } else {
                    throw new SipException("Could not forward request.",
                        SipException.GENERAL_ERROR);
                }

            } catch (IOException ex) {
                continue;
            }
        }
    }

    /**
     * Sends specified {@link Response} and returns void i.e.
     * no transaction record is associated with this action. This method implies
     * that the application is functioning as either a stateless proxy or a
     * stateless User Agent Server.
     * <ul>
     *  <li> Stateless proxy - A stateless proxy simply forwards every response
     *  it receives upstream and discards information about the response message
     *  once the message has been forwarded. A stateless proxy does not
     *  have any notion of a transaction.
     *  <li>Stateless User Agent Server - A stateless UAS does not maintain
     *  transaction state. It replies to requests normally, but discards
     *  any state that would ordinarily be retained by a UAS after a response
     *  has been sent.  If a stateless UAS receives a retransmission of a
     *  request, it regenerates the response and resends it, just as if it
     *  were replying to the first instance of the request. A UAS cannot be
     *  stateless unless the request processing for that method would always
     *  result in the same response if the requests are identical. Stateless
     *  UASs do not use a transaction layer; they receive requests directly
     *  from the transport layer and send responses directly to the transport
     *  layer.
     * </ul>
     *
     * @see Response
     * @param sipResponse the Response to send statelessly.
     * @throws IOException if I/O error occured
     * @throws SipException if implementation cannot send response for
     * any other reason
     * @see Response
     * @since v1.1
     */
    public void sendResponse(Response sipResponse)
            throws IOException, SipException {
        ViaHeader via = sipResponse.getTopmostVia();
        if (via == null) {
            throw new SipException("No via header in response!",
                SipException.INVALID_MESSAGE);
        }

        int    port = via.getPort();
        String transport = via.getTransport();
        // check to see if Via has "received paramaeter". If so
        // set the host to the via parameter. Else set it to the
        // Via host.
        String host = via.getReceived();

        if (host == null) {
            host = via.getHost();
        }

        if (port == -1) {
            port = 5060; // IMPL_NOTE: move to SIPConstants
        }

        Hop hop = new Hop(host + ":" + port + "/" + transport);
        MessageChannel messageChannel = sipStack.createRawMessageChannel(hop);
        messageChannel.sendMessage(sipResponse);
    }

    /**
     * This method sets the listening point of the SipProvider.
     * A SipProvider can only have a single listening point at any
     * specific time. This method returns
     * silently if the same <var>listeningPoint</var> argument is re-set
     * on the SipProvider.
     * <p>
     * JAIN SIP supports recieving messages from
     * any port and interface that a server listens on for UDP, on that same
     * port and interface for TCP in case a message may need to be sent
     * using TCP, rather than UDP, if it is too large. In order to satisfy this
     * functionality an application must create two SipProviders and set
     * identical listeningPoints except for transport on each SipProvder.
     * <p>
     * Multiple SipProviders are prohibited to listen on the same
     * listening point.
     *
     * @param listeningPoint of this SipProvider
     * @see ListeningPoint
     * @since v1.1
     */
    public void setListeningPoint(ListeningPoint listeningPoint)  {
        if (listeningPoint == null)
            throw new NullPointerException("Null listening point");
        ListeningPoint lp = (ListeningPoint) listeningPoint;
        lp.sipProviderImpl = this;
        this.listeningPoint = (ListeningPoint) listeningPoint;

    }

    /**
     * Invoked when an error has ocurred with a transaction.
     * Propagate up to the listeners.
     *
     * @param transactionErrorEvent Error event.
     */
    public void transactionErrorEvent
            (SIPTransactionErrorEvent transactionErrorEvent) {
        Transaction transaction =
                (Transaction) transactionErrorEvent.getSource();

        if (transactionErrorEvent.getErrorID() ==
                SIPTransactionErrorEvent.TRANSPORT_ERROR) {

            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                    "TransportError occured on " + transaction);
            }

            //  handle Transport error as timeout.
            Object errorObject = transactionErrorEvent.getSource();
            Timeout timeout = Timeout.TRANSACTION;
            TimeoutEvent ev = null;

            if (errorObject instanceof ServerTransaction) {
                ev = new TimeoutEvent(this, (ServerTransaction)
                errorObject);
            } else {
                ev = new TimeoutEvent(this, (ClientTransaction)
                errorObject,
                        timeout);
            }
            this.handleEvent(ev, (Transaction) errorObject);

        } else {
            //  This is a timeout event.
            Object errorObject = transactionErrorEvent.getSource();
            Timeout timeout = Timeout.TRANSACTION;
            TimeoutEvent ev = null;

            if (errorObject instanceof ServerTransaction) {
                ev = new TimeoutEvent(this, (ServerTransaction)
                errorObject);
            } else {
                ev = new TimeoutEvent(this, (ClientTransaction)
                errorObject,
                        timeout);
            }
            this.handleEvent(ev, (Transaction) errorObject);

        }
    }
}