FileDocCategorySizeDatePackage
StackConnector.javaAPI DocphoneME MR2 API (J2ME)23324Wed May 02 18:00:42 BST 2007gov.nist.microedition.sip

StackConnector.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.
 */
/*
 * StackConnector.java
 *
 * Created on Feb 11, 2004
 *
 */
package gov.nist.microedition.sip;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Random;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.ServerSocketConnection;

import javax.microedition.sip.SipClientConnection;
import javax.microedition.sip.SipConnectionNotifier;
import javax.microedition.sip.SipDialog;

import gov.nist.microedition.io.j2me.sip.DistributedRandom;
import gov.nist.siplite.ConfigurationProperties;
import gov.nist.siplite.ListeningPoint;
import gov.nist.siplite.ObjectInUseException;
import gov.nist.siplite.PeerUnavailableException;
import gov.nist.siplite.RequestEvent;
import gov.nist.siplite.ResponseEvent;
import gov.nist.siplite.SipFactory;
import gov.nist.siplite.SipListener;
import gov.nist.siplite.SipProvider;
import gov.nist.siplite.SipStack;
import gov.nist.siplite.TimeoutEvent;
import gov.nist.siplite.TooManyListenersException;
import gov.nist.siplite.TransportNotSupportedException;
import gov.nist.siplite.address.AddressFactory;
import gov.nist.siplite.address.SipURI;
import gov.nist.siplite.header.HeaderFactory;
import gov.nist.siplite.header.AcceptContactHeader;
import gov.nist.siplite.message.MessageFactory;
import gov.nist.siplite.message.Request;
import gov.nist.siplite.message.Response;
import gov.nist.siplite.stack.ClientTransaction;
import gov.nist.siplite.stack.ServerTransaction;
import gov.nist.core.NameValueList;

import com.sun.midp.security.SecurityToken;
import gov.nist.siplite.SIPConstants;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * This class is the connector between the JSR180 and
 * the nist-siplite stack. This class create a stack from the
 * SipConnector.open(SIP_URI) with the listening point equals
 * to the one specified in the SIP URI. If none is specified, a random one is
 * allowed by the system.
 * This class receive the messages from the stack because it's implementing the
 * SipListener class and transmit them to either SipConnectionNotifier or
 * SipClientConnection or both.
 * This class follow the singleton design pattern and is thread-safe
 *
 *
 * <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
 */
public class StackConnector implements SipListener {

    /**
     * Security token for SIP/SIPS protocol class
     */
    private SecurityToken classSecurityToken;
    /**
     * The unique instance of this class
     */
    private static StackConnector instance = null;
    /**
     * The actual stack
     */
    protected SipStack sipStack = null;
    /**
     * listen address
     */
    private String localAddress = null;
    /**
     * Temporary listening point.
     */
    private ListeningPoint tempListeningPoint = null;
    /**
     * Temporary sip provider.
     */
    private SipProvider tempSipProvider = null;
    /**
     * list of connection notifiers
     */
    protected Vector connectionNotifiersList = null;
    /**
     * list of client connections
     */
    // protected Vector clientConnectionList = null;
    /**
     * list of all current dialogs
     */
    protected Vector sipDialogList = null;
    /**
     * Address factory handle.
     */
    public static AddressFactory addressFactory = null;
    /**
     * Message factory handle.
     */
    public static MessageFactory messageFactory = null;
    /**
     * Header factory handle.
     */
    public static HeaderFactory  headerFactory = null;
    /**
     * Shared listening point
     */
    private ListeningPoint sharedListeningPoint = null;
    /**
     * Shared sipProvider instance
     */
    private SipProvider sharedSipProvider = null;
    /**
     * Shared port number
     */
    int sharedPortNumber = -1;

    /**
     * Indicates mime types used by applications for SipConnectionNotifiers
     * in shared mode
     */
    Vector sharedMimeTypes = null;

    static {
        // Creates the factories to help to construct messages
        messageFactory = new MessageFactory();
        addressFactory = new AddressFactory();
        headerFactory = new HeaderFactory();
    }

    /**
     * Constructor
     * Creates the stack
     * @param classSecurityToken security token for SIP/SIPS protocol class
     */
    private StackConnector(SecurityToken classSecurityToken)
    throws IOException {
        com.sun.midp.io.j2me.socket.Protocol conn;

        connectionNotifiersList = new Vector();
        // clientConnectionList = new Vector();
        sipDialogList = new Vector();
        // Create the sipStack
        SipFactory sipFactory  =  SipFactory.getInstance();
        ConfigurationProperties properties = new ConfigurationProperties();
        int randomPort = new DistributedRandom().nextInt(60000)+1024;

        /*
         * Original NIST method for opening the serversocket connection
         * has been replaced by direct calls to instantiate the protocol
         * handler, in order to pass the security token for use of lower
         * level transport connection.
         * Original NIST sequence is :
         *
         * ServerSocketConnection serverSoc =
         *      (ServerSocketConnection)Connector.open("socket://:"+randomPort);
         *
         */

        conn = new com.sun.midp.io.j2me.socket.Protocol();
        ServerSocketConnection serverSoc =
                (ServerSocketConnection)conn.openPrim(classSecurityToken,
                "//:" + randomPort);

        localAddress = serverSoc.getLocalAddress();
        properties.setProperty("javax.sip.IP_ADDRESS", localAddress);
        properties.setProperty("javax.sip.STACK_NAME", "shootme");
        properties.setProperty("gov.nist.javax.sip.LOG_FILE_NAME",
                "/tmp/jsr180-log");
        properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL",
                "0"); // TRACE_PRIVATE

        // Initialise the log file
        serverSoc.close();
        try {
            //  Create SipStack object
            sipStack = sipFactory.createSipStack(properties,
                    classSecurityToken);
            sipStack.setStackConnector(this);
        } catch (PeerUnavailableException e) {
            //  SipStackImpl in the classpath
            // e.printStackTrace();
            throw new IOException(e.getMessage());
        }
        // save security token
        this.classSecurityToken = classSecurityToken;
    }

    /**
     * Get the unique instance of this class
     * @param classSecurityToken security token for SIP/SIPS protocol class
     * @return the unique instance of this class
     */
    public synchronized static StackConnector getInstance(SecurityToken
            classSecurityToken)
            throws IOException {
        if (instance == null)
            instance = new StackConnector(classSecurityToken);
        return instance;
    }

    /**
     * remove the instance of the stack.
     */
    public synchronized static void releaseInstance() {
        instance = null;
    }

    /**
     * Creates a sip connection notifier in shared mode. In shared mode,
     * SipConnectionNotifier is supposed to use a single shared port or
     * listening point for all applications
     *
     * @param secure  flag to specify whether to use or not the secure layer
     * @param transport  transport protocol name
     * @param mimeType parameter for filtering incomming SIP packets or null
     * @return the sip connection notifier that will receive request
     * @throws IOException  if we cannot create the sip connection notifier
     * for whatsoever reason
     */
    public SipConnectionNotifier createSharedSipConnectionNotifier(
            boolean secure,
            String transport,
            String mimeType)
            throws IOException {

        if (sharedMimeTypes != null) {
            for (int i = 0; i < sharedMimeTypes.size(); i++) {
                if (mimeType.equalsIgnoreCase(
                        ((String)sharedMimeTypes.elementAt(i)))) {
                    throw new IOException("Application type is already " +
                            "reserved");
                }
            }
        } else {
            sharedMimeTypes = new Vector();
        }

        /*
         * Add the mimeType to sharedMimeTypes vector
         */
        sharedMimeTypes.addElement((String)mimeType);

        /*
         * SipConnectionNotifier in shared mode must use shared system SIP port
         * and shared SIP identity. So a shared listening point and sipProvider
         * must be used for every SipConnectionNotifier in shared mode
         */

        if ((sharedListeningPoint == null) &&
            (sharedSipProvider == null)) {

            // select a free port
            sharedPortNumber = selectPort(sharedPortNumber, transport);

            sharedListeningPoint =
                this.tempListeningPoint; // initialized by "selectPort()"
            sharedSipProvider =
                this.tempSipProvider; // initialized by "selectPort()"
        }

        SipConnectionNotifier sipConnectionNotifier =
                new SipConnectionNotifierImpl(sharedSipProvider, localAddress,
                sharedPortNumber, this.classSecurityToken, mimeType, true);
        // ((SipConnectionNotifierImpl)sipConnectionNotifier).start();
        // Add the the newly created sip connection notifier to the list of
        // connection notifiers
        this.connectionNotifiersList.addElement(sipConnectionNotifier);

        return sipConnectionNotifier;
    }

    /**
     * Create a listening point ans sip provider on given or random
     * selected port. When port is selected successfully,
     * ListeningPoint and SipProvider instances are created.
     * @param portNumber the number of the port on which we must listen
     * for incoming requests or -1 to select random port
     * @param transport transport protocol name
     * @return the selected port number
     * @throws IOException if given port is busy
     */
    private int selectPort(int portNumber, String transport)
        throws IOException {

        boolean isRandomPort = (portNumber == -1);
        final int MAX_ATTEMPTS = 1000;
        int attemps = 0;

        if (isRandomPort) {
            // Try the default port first.
            portNumber = SIPConstants.DEFAULT_NONTLS_PORT;
        }
        
        do {
            if (attemps++ > MAX_ATTEMPTS) {
                throw new IOException("Cannot select a port!");
            }

            // Creates the listening point
            // IMPL_NOTE : Use the parameters to restrain the incoming messages
            try {
                tempListeningPoint =
                    sipStack.createListeningPoint(portNumber, transport);
            } catch (TransportNotSupportedException tnse) {
                // tnse.printStackTrace();
                throw new IOException(tnse.getMessage());
            } catch (IllegalArgumentException iae) {
                if (isRandomPort) { // port is busy
                    // Select a random port from 1024 to 10000.
                    portNumber = new DistributedRandom().nextInt(8975) + 1024;
                    continue;
                } else {
                    throw new IOException(iae.getMessage());
                }
            }

            // Creates the sip provider
            try {
                tempSipProvider =
                    sipStack.createSipProvider(tempListeningPoint);
            } catch (ObjectInUseException oiue) {
                if (isRandomPort) { // port is busy
                    // Select a random port from 1024 to 10000.
                    portNumber = new DistributedRandom().nextInt(8975) + 1024;
                    continue;
                } else {
                    // oiue.printStackTrace();
                    throw new ObjectInUseException(oiue.getMessage());
                }
            }
            
            isRandomPort = false;
            
            // Add this class as a listener for incoming messages
            try {
                tempSipProvider.addSipListener(this);
            } catch (TooManyListenersException tmle) {
                // tmle.printStackTrace();
                throw new IOException(tmle.getMessage());
            }
        } while (isRandomPort);

        return portNumber;
    }

    /**
     * Create a sip connection notifier on a specific port
     * using or not the sip secure layer and with some restrictive parameters
     * to receive requests
     * @param portNumber the number of the port on which we must listen
     * for incoming requests or -1 to select random port
     * @param secure flag to specify whether to use or not the secure layer
     * @param transport transport protocol name
     * @param mimeType parameter for filtering incomming SIP packets or null
     * @return the sip connection notifier that will receive request
     * @throws IOException if we cannot create the sip connection notifier
     * for whatsoever reason
     */
    public SipConnectionNotifier createSipConnectionNotifier(
        int portNumber,
        boolean secure,
        String transport,
        String mimeType)
        throws IOException {

        // select a free port (if need)
        portNumber = selectPort(portNumber, transport);

        SipConnectionNotifier sipConnectionNotifier =
                new SipConnectionNotifierImpl(tempSipProvider, localAddress,
                portNumber, this.classSecurityToken, mimeType, false);
        // ((SipConnectionNotifierImpl)sipConnectionNotifier).start();
        // Add the the newly created sip connection notifier to the list of
        // connection notifiers
        this.connectionNotifiersList.addElement(sipConnectionNotifier);

        return sipConnectionNotifier;
    }

    /**
     * Creates a sip Client Connection to send a request to the
     * following SIP URI user@host:portNumber;parameters
     * @param inputURI input SIP URI
     * @return the sip client connection
     */
    public SipClientConnection createSipClientConnection(SipURI inputURI) {

        SipClientConnection sipClientConnection =
                new SipClientConnectionImpl(inputURI, this.classSecurityToken);
        return sipClientConnection;
    }

    /**
     * Gets the current connection notifier list.
     * @return the connection notifier list
     */
    public Vector getConnectionNotifiersList() {
        return connectionNotifiersList;
    }

    /**
     * Gets the current SipStack object.
     * @return the current SipStack object
     */
    public SipStack getSipStack() {
        return sipStack;
    }

    /**
     * Retrieve from the list of connection notifier the one that use the same
     * port as in parameter
     *
     * @param portNumber the port number
     * @param acceptContactType MIME type as in Accept-Contact header
     *
     * @return the connection notifier matching the same port
     */
    public SipConnectionNotifier getSipConnectionNotifier(int portNumber,
            String acceptContactType) {
        Enumeration e = connectionNotifiersList.elements();
        while (e.hasMoreElements()) {
            SipConnectionNotifier sipConnectionNotifier =
                    (SipConnectionNotifier)e.nextElement();
            try {
                if (sipConnectionNotifier.getLocalPort() != portNumber) {
                    continue;
                }

                if (acceptContactType != null) {
                    String scnMimeType =
                            ((SipConnectionNotifierImpl)sipConnectionNotifier).
                            getMIMEType();
                    if (scnMimeType != null) {
                        if (scnMimeType.equalsIgnoreCase(acceptContactType) ||
                                "*".equals(acceptContactType)) {
                            return sipConnectionNotifier;
                        }
                    }
                } else {
                    return sipConnectionNotifier;
                }
            } catch (IOException ioe) {
                // Intentionally ignored.
            }
        }
        return null;
    }


    /**
     * generate a random tag that can be used either in the FromHeader or in the
     * ToHeader
     * @return the randomly generated tag
     */
    protected static String generateTag() {
        return String.valueOf(new Random().nextInt(Integer.MAX_VALUE));
    }

    /**
     * find in the dialog list, the sip dialog with the same dialog ID
     * as the one in parameter
     * @param dialogID dialogID to test against
     * @return the sip dialog with the same dialog ID
     */
    protected SipDialog findDialog(String dialogID) {
        Enumeration e = sipDialogList.elements();
        while (e.hasMoreElements()) {
            SipDialog sipDialog = (SipDialog)e.nextElement();
            if (sipDialog.getDialogID() != null &&
                    sipDialog.getDialogID().equals(dialogID)) {
                return sipDialog;
            }
        }
        return null;
    }


    /**
     * Processes the current transaction event.
     * @param requestEvent the protocol transition event
     */
    public void processRequest(RequestEvent requestEvent) {
        try {
            String acceptContactType = null;
            Request request = requestEvent.getRequest();
            AcceptContactHeader acHdr = request.getAcceptContact();
            if (acHdr != null) { // Accept-Contact header present
                acceptContactType = acHdr.getType();
            }

            // Retrieve the SipConnectionNotifier from the transaction
            SipConnectionNotifierImpl sipConnectionNotifier = null;
            ServerTransaction serverTransaction =
                    requestEvent.getServerTransaction();
            if (serverTransaction != null)
                sipConnectionNotifier =
                        (SipConnectionNotifierImpl)serverTransaction
                        .getApplicationData();
            // If it's a new request coming in, the
            // sipConnectionNotifier will certainly be null, so
            // retrieve from the list of connection notifier the one
            // that use the same port as in parameter
            if (sipConnectionNotifier == null) {
                SipProvider sipProvider = (SipProvider)requestEvent.getSource();
                ListeningPoint listeningPoint = sipProvider.getListeningPoint();
                sipConnectionNotifier =
                        ((SipConnectionNotifierImpl)
                        getSipConnectionNotifier(listeningPoint.getPort(),
                            acceptContactType));
                if ((serverTransaction != null) &&
                    (sipConnectionNotifier != null)) {
                    serverTransaction.setApplicationData(sipConnectionNotifier);
                }
            }

            if (sipConnectionNotifier != null) {
                sipConnectionNotifier.notifyRequestReceived(request);
            } else {
                // No need to throw any RuntimeException; just log the error
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_JSR180,
                        "we cannot find any connection notifier" +
                            "matching to handle this request");
                }
            }
        } catch (NullPointerException npe) {
            // npe.printStackTrace();
        } catch (IllegalArgumentException iae) {
            // iae.printStackTrace();
        }
    }

    /**
     * Processes the resposne event.
     * @param responseEvent the transition reply event
     */
    public void processResponse(ResponseEvent responseEvent) {
        try {
            Response response = responseEvent.getResponse();

            // Retrieve the SipClientConnection from the transaction
            ClientTransaction clientTransaction =
                    responseEvent.getClientTransaction();
            SipClientConnectionImpl sipClientConnection =
                (SipClientConnectionImpl)clientTransaction.getApplicationData();

            if (sipClientConnection != null) {
                // translate null when client transactions are same
                if (sipClientConnection.getClientTransaction()
                    .equals(clientTransaction)) {
                    sipClientConnection.notifyResponseReceived(response, null);
                } else { // send new client transaction
                    sipClientConnection.notifyResponseReceived(response,
                        clientTransaction);
                }
            } else {
                throw new RuntimeException(
                    "we cannot find any client connection" +
                        "matching to handle this request");
            }
        } catch (NullPointerException npe) {
            // npe.printStackTrace();
        } catch (IllegalArgumentException iae) {
            // iae.printStackTrace();
        }
    }

    /**
     * Process a timeout event.
     * @param timeoutEvent state transition timeout event
     */
    public void processTimeout(TimeoutEvent timeoutEvent) {

    }

    /**
     * Close a SipConnectionNotifier in shared mode
     *
     * @param mimeType MIME type of the SipConnectionNotifier
     */
    public void closeSharedSipConnectionNotifier(String mimeType)
    throws IOException {

        // Ignore when sharedMimeTypes already removed
        if (sharedMimeTypes == null) {
            return;
        }

        sharedMimeTypes.removeElement((String)mimeType);

        if (sharedMimeTypes.size() == 0) {
            try {
                sipStack.deleteListeningPoint(sharedListeningPoint);
                sipStack.deleteSipProvider(sharedSipProvider);
            } catch (ObjectInUseException oiue) {
                throw new IOException(oiue.getMessage());
            }

            sharedMimeTypes = null;

            /*
             * There are no more associated listening points in shared mode;
             * so stop the stack
             */
            sipStack.stopStack();
        }
    }
}