javaextensions/wma/sms_cbs/javasrc/com/nokia/mj/impl/sms/SMSConnectionImpl.java
changeset 21 2a9601315dfc
child 78 71ad690e91f5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javaextensions/wma/sms_cbs/javasrc/com/nokia/mj/impl/sms/SMSConnectionImpl.java	Mon May 03 12:27:20 2010 +0300
@@ -0,0 +1,757 @@
+/*
+* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description:
+*
+*/
+
+package com.nokia.mj.impl.sms;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import javax.wireless.messaging.Message;
+import javax.wireless.messaging.TextMessage;
+import javax.wireless.messaging.BinaryMessage;
+import javax.wireless.messaging.MessageListener;
+import javax.wireless.messaging.MessageConnection;
+
+import com.nokia.mj.impl.utils.Logger;
+import com.nokia.mj.impl.smscbs.utils.WmaUrl;
+import com.nokia.mj.impl.rt.support.ShutdownListener;
+import com.nokia.mj.impl.rt.support.ApplicationUtils;
+/**
+ * The SMSConnectionImpl used for sending and receiving Binary/Text Messages. It
+ * offers methods for creating Binary/Text Messages and for sending them. It
+ * implements the receiving functionality.
+ */
+public class SMSConnectionImpl implements MessageConnection
+{
+    private static final String SERVER_MSG =
+        "Message Connection must be a Server connection";
+
+    private static final int INITIAL = 0;
+
+    private static final int OPEN = 1;
+
+    private static final int CLOSED = 2;
+
+    private static final int ERROR = 3;
+
+    private static final int MAXIMUM_MESAGE_SEGMENTS_SUPPORTED = 10;
+
+    //current Connection state
+    private int iState;
+
+    // any error while receiving message
+    private int iRecvError;
+
+    //status of send
+    private int iSendResult;
+
+    // waiting for message
+    private int iMessageWaiting;
+
+    // number of received messages
+    private int iMessagesOnQueue;
+
+    // number of notifications to be done
+    private int iMessageNotificationsToMake;
+
+    // handle to the native side
+    private  int iNativeHandle;
+
+    // any message notification missed
+    private boolean iMissedMessage;
+
+    // Is Server Connection
+    private boolean iServerConnection;
+
+    // synchronization for reading message
+    private final Object iReadLock;
+
+    // synchronization for sending message
+    private final Object iWriteLock;
+
+    // synchronization for closing
+    private final Object iCloseLock;
+
+    // synchronization for receiving
+    private final Object iMessageLock;
+
+    // synchronization for sending
+    private final Object iSendLock;
+
+    // the listener used for messages receiving notifications
+    private MessageListener iMessageListener;
+
+    private WmaUrl iUri;
+
+    public SMSConnectionImpl(WmaUrl aMsgUri, boolean aServerMode)
+    throws IOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::SMSConnectionImpl");
+        iUri = aMsgUri;
+        iState = INITIAL;
+        iServerConnection = aServerMode;
+        //create the native side peer
+        iNativeHandle = _createConnection(iUri.getAbsoluteUri(),
+                                          iServerConnection);
+        iReadLock = new Object();
+        iWriteLock = new Object();
+        iCloseLock = new Object();
+        iMessageLock = new Object();
+        iSendLock = new Object();
+        //register for shutdown listening
+        setShutdownListener();
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- SMSConnectionImpl::SMSConnectionImpl");
+    }
+
+    /*
+     * This function registers this object for shutDown.
+     */
+    private void setShutdownListener()
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::setShutdownListener");
+        // Get the insatnce of ApplicationUtils.
+        ApplicationUtils appUtils = ApplicationUtils.getInstance();
+
+        // Get the name of the application.
+        appUtils.addShutdownListener(new ShutdownListener()
+        {
+            //The method that gets called when Application is shutting down
+            public void shuttingDown()
+            {
+                try
+                {
+                    close();
+                }
+                catch (IOException ex)
+                {
+                    //Nothing to do, just ignore
+                    Logger.ELOG(Logger.EWMA, ex.toString(), ex);
+                }
+            }
+        });
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- SMSConnectionImpl::setShutdownListener");
+    }
+
+    /**
+     * open() is only called when a MIDlet calls Connector.open() a MIDlet will
+     * not be able to access the Connection until open() has completed and a
+     * reference has been returned. It should then only be called once, by only
+     * one thread. particularly, a Connection cannot be closed by a MIDlet until
+     * it has been fully opened.
+     * @exceptions Throws IOException if any error while Opening
+     */
+    public void open() throws IOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,"+ SMSConnectionImpl::open");
+        if (iState == INITIAL)
+        {
+            iState = OPEN;
+            //in case of server connection a notifier thread is created which
+            //waits in the native side and informs the application when there
+            //is an incoming message.
+            if (iServerConnection)
+            {
+                synchronized (iCloseLock)
+                {
+                    new Thread(new Runnable()
+                    {
+                        public void run()
+                        {
+                            _openConnection(iNativeHandle);
+                        }
+                    }).start();
+                }
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,"- SMSConnectionImpl::open");
+    }
+
+    /**
+     * close() is only called when a MIDlet calls close() on a connection that
+     * is opened by calling Connector.open() A MIDlet will not be able to access
+     * the Connection after calling close() on that connection.
+     * @exceptions Throws IOException if any error while closing.
+     */
+    public void close() throws IOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,"+ SMSConnectionImpl::close");
+        synchronized (iCloseLock)
+        {
+            if (iState != CLOSED)
+            {
+                iState = CLOSED;
+                _closeConnection(iNativeHandle);
+
+                //notify the notifier thread if it is waiting for a receive() or
+                //setMessageListener() to be called
+                synchronized (iMessageLock)
+                {
+                    iMessageLock.notify();
+                }
+                //notify the receive operation about connection closed
+                synchronized (iReadLock)
+                {
+                    iReadLock.notify();
+                }
+                _dispose(iNativeHandle);
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,"- SMSConnectionImpl::close");
+    }
+
+    /**
+     * Sets the message listener for this connection
+     *
+     * @param aListener
+     *            listener interested for incoming message.
+     * @exceptions throws IOException if the connection is already closed
+     *             or this method is called on a client connection.
+     */
+    public void setMessageListener(MessageListener aListener)
+    throws IOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::setMessageListener");
+        if (!iServerConnection)
+        {
+            throw new IOException(SERVER_MSG);
+        }
+
+        if (iState == CLOSED)
+        {
+            throw new IOException(
+                "Setting message listener failed: connection is already closed");
+        }
+        checkReceivePermission();
+        synchronized (iReadLock)
+        {
+            iMessageListener = aListener;
+            if (aListener != null)
+            {
+                synchronized (iMessageLock)
+                {
+                    iMessageLock.notify();
+                }
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::setMessageListener");
+    }
+
+    /**
+     * checks for any error while receiving a message
+     * @exception throws IOException in case of any error
+     */
+    private final void checkForReceiveError() throws IOException
+    {
+        if (iRecvError !=0)
+        {
+            int err = iRecvError;
+            iRecvError = 0;
+            if (err < 0)
+            {
+                _checkError(err, false);
+            }
+        }
+    }
+
+    /**
+     * Checks for security permission for receive operation.
+     * @exception throws SecurityException if no valid permission exists.
+     */
+    protected void checkReceivePermission()
+    {
+        ApplicationUtils appUtils = ApplicationUtils.getInstance();
+        SMSPermissionImpl permission = new SMSPermissionImpl("sms://*",
+                "receive");
+        appUtils.checkPermission(permission);
+    }
+    /**
+     * Receive is a 3 step process: 1) Wait for some incoming message 2)
+     * Retrieve the the message 3) Construct a Binary/Text Message
+     *
+     * @exceptions  throws IOException if the connection is already closed.
+     * @exceptions  throws InterruptedIOException if the connection is closed
+     *             while receiving
+     * @returns the constructed message
+     */
+    public Message receive() throws IOException, InterruptedIOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,"+ SMSConnectionImpl::receive");
+        if (!iServerConnection)
+        {
+            //throw IOException if receive is called on client connection
+            throw new IOException(SERVER_MSG);
+        }
+        Message message = null;
+        synchronized (iReadLock)
+        {
+            //check for connection
+            if (iState == CLOSED)
+            {
+                throw new IOException(
+                    "Receiving message failed: connection is already closed");
+            }
+            if (iUri.isRestrictedPort() == true)
+            {
+                throw new SecurityException(
+                    "No permission to receive on port: " + iUri.getPort());
+            }
+            checkReceivePermission();
+            //if no messages were there to receive wait till some message
+            //arrives
+            if (iMessagesOnQueue == 0)
+            {
+                iMessageWaiting++;
+                //notify the notifier thread
+                synchronized (iMessageLock)
+                {
+                    iMessageLock.notify();
+                }
+                //wait for incoming message notification
+                try
+                {
+                    Logger.LOG(Logger.EWMA, Logger.EInfo,
+                               "WMA : Waiting for message");
+                    iReadLock.wait();
+                }
+                catch (InterruptedException ex)
+                {
+                    Logger.ELOG(Logger.EWMA, ex.toString(), ex);
+                }
+            }
+            //check for any error while receiving
+            checkForReceiveError();
+            synchronized (iCloseLock)
+            {
+                String remoteAddress;
+                //if the connection was closed throw InterruptedIOException
+                if (iState == CLOSED)
+                {
+                    throw new InterruptedIOException(
+                        "Connection closed while receiving message");
+                }
+                //get the message type ( Text/Binary)
+                final int messageType = _getMessageType(iNativeHandle);
+                if (messageType != SMSMessageImpl.MESSAGE_TEXT &&
+                        messageType != SMSMessageImpl.MESSAGE_BINARY)
+                {
+                    if (messageType < 0)
+                    {
+                        _checkError(messageType, false);
+                    }
+                }
+                //get the remote host address
+                remoteAddress = _getHostAddress(iNativeHandle);
+                //get the time stamp of the message
+                final long timestamp = _getTimestamp(iNativeHandle);
+                //construct the message object based on the message type
+                if (messageType == SMSMessageImpl.MESSAGE_TEXT)
+                {
+                    //get the text message data.
+                    final String textData = (String)_getReceivedMessageData(
+                                                iNativeHandle, messageType);
+                    //construct the text message object
+                    TextMessage textMessage = new SMSTextMessageImpl(
+                        remoteAddress, timestamp);
+                    textMessage.setPayloadText(textData);
+                    message = textMessage;
+                }
+                else
+                {
+                    //get the Binary message data
+                    final byte[] binaryData = (byte[]) _getReceivedMessageData(
+                                                  iNativeHandle, messageType);
+                    //construct the binary message object
+                    BinaryMessage binaryMessage = new SMSBinaryMessageImpl(
+                        remoteAddress, timestamp);
+                    binaryMessage.setPayloadData(binaryData);
+                    message = binaryMessage;
+                }
+            }
+            iMessagesOnQueue--;
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,"- SMSConnectionImpl::receive");
+        return message;
+    }
+
+    /**
+     * Method for creating a Binary/Text Message object. If it is a client
+     * connection The destination address of the newly created messages will be
+     * set to the destination part of the URI provided when this connection was
+     * created.
+     *
+     * @param aType
+     *            The type of the message to be created. It can only be
+     *            "Binary/Text"
+     */
+    public Message newMessage(String aType)
+    {
+        String address = null;
+        if (!iServerConnection)
+        {
+            address = iUri.getAbsoluteUri();
+        }
+
+        return newMessage(aType, address);
+    }
+
+    /**
+     * Method for creating a Binary/Text Message object
+     *
+     * @param aType
+     *            The type of the message to be created. It can only be
+     *            "Binary/Text"
+     * @param aAddress
+     *            the destination address of the Message
+     */
+    public Message newMessage(String aType, String aAddress)
+    {
+        Message msg;
+        if (aType.equals(TEXT_MESSAGE))
+        {
+            msg = new SMSTextMessageImpl(aAddress, 0);
+        }
+        else if (aType.equals(BINARY_MESSAGE))
+        {
+            msg = new SMSBinaryMessageImpl(aAddress, 0);
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "Message type is invalid: " + aType);
+        }
+
+        return msg;
+    }
+
+    /**
+     * Returns the number of segments required for sending a
+     *  Binary/Text Message.
+     * @param aMsg The Message for which the segments to be found.
+     * @exceptions  throws IllegalArguementException if no address is specified
+     *               or if the message is not a valid message
+     */
+    public int numberOfSegments(Message aMsg)
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::numberOfSegments");
+        int messageSegments = 0;
+        if (aMsg == null)
+        {
+            throw new NullPointerException("Message parameter is null");
+        }
+        String address = aMsg.getAddress();
+        // if it is a serverConnection and address field of the message is null
+        // then it calculates numberOfSegments assuming Port number present.
+        // Because that will be the maximum numberOfSegments required to send
+        // the message.
+        final WmaUrl uri;
+        if (iServerConnection && address != null)
+        {
+            uri = new WmaUrl(address);
+            uri.validateUri();
+        }
+        else
+        {
+            uri = iUri;
+        }
+
+        final SMSMessageImpl messageImpl;
+        try
+        {
+            messageImpl = (SMSMessageImpl) aMsg;
+        }
+        catch (ClassCastException ex)
+        {
+            throw new IllegalArgumentException("NumberOfSegments can only"
+                                               + "be calculated for Text/Binary Message");
+        }
+        synchronized (iCloseLock)
+        {
+            if (iState != CLOSED)
+            {
+                if (messageImpl.getType() == messageImpl.MESSAGE_TEXT)
+                {
+
+                    messageSegments = _smsTextDataSegments(iNativeHandle,
+                                                           messageImpl.getType(), uri.getHost(), uri.getPort(),
+                                                           messageImpl.getPayloadTextData());
+                }
+                else
+                {
+
+                    messageSegments = _smsBinaryDataSegments(iNativeHandle,
+                                      messageImpl.getType(), uri.getHost(), uri.getPort(),
+                                      messageImpl.getPayloadBinaryData());
+                }
+            }
+        }
+        if (messageSegments > MAXIMUM_MESAGE_SEGMENTS_SUPPORTED)
+        {
+            messageSegments = 0;
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::numberOfSegments");
+        return messageSegments;
+    }
+
+    /**
+     * Method for sending a certain message. Only Binary/Text Messages can be
+     * sent through this connection. The destination addresses of the Message to
+     * be sent have to contain the destination address .
+     *
+     * @param aMsg
+     *            The message to be sent
+     * @exceptions throws IOException if there is any n/w problem or if
+     *             connection is already closed
+     * @exceptions throws InterruptedIOException if a timeout occurs or if
+     *               the connection is closed while sending.
+     * @exceptions throws IllegalArguementException if No address or host is
+     *               specified
+     */
+    public void send(Message aMsg) throws IOException, InterruptedIOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo, "+ SMSConnectionImpl::send");
+        int messageSegments = 0;
+        if (aMsg == null)
+        {
+            throw new NullPointerException("Message parameter is null");
+        }
+        String sendAddress = aMsg.getAddress();
+        //check whether destination address is specified in the message
+        if (sendAddress == null)
+        {
+            throw new IllegalArgumentException(
+                "Message destination address not specified");
+        }
+        final WmaUrl uri = new WmaUrl(sendAddress);
+        // validate the address
+        uri.validateUri();
+        // check whether destination host address is specified in the message
+        if ("".equals(uri.getHost()))
+        {
+            throw new IllegalArgumentException(
+                "Message destination address not specified");
+        }
+        final SMSMessageImpl messageImpl;
+        try
+        {
+            messageImpl = (SMSMessageImpl) aMsg;
+        }
+        catch (ClassCastException ex)
+        {
+            throw new IllegalArgumentException("The message to be sent with"
+                                               + "this connection can only be Text/Binary Message");
+        }
+        // validates the port
+        if (uri.isRestrictedPort() == true)
+        {
+            throw new SecurityException(
+                "No permission to send on port: " + uri.getPort());
+        }
+        //to synchronize send functionality
+        synchronized (iWriteLock)
+        {
+            synchronized (iSendLock)
+            {
+                // send operation should not be carried out while close
+                // is in progress
+                synchronized (iCloseLock)
+                {
+                    //verify whether connection is closed.
+                    if (iState == CLOSED)
+                    {
+                        throw new IOException(
+                            "Sending message failed: connection is already closed");
+                    }
+                    //calculate the number of message segments required to send this
+                    //message
+                    if (messageImpl.getType() == messageImpl.MESSAGE_TEXT)
+                    {
+
+                        messageSegments = _smsTextDataSegments(iNativeHandle,
+                                                               messageImpl.getType(), uri.getHost(), uri.getPort(),
+                                                               messageImpl.getPayloadTextData());
+                    }
+                    else
+                    {
+
+                        messageSegments = _smsBinaryDataSegments(iNativeHandle,
+                                          messageImpl.getType(), uri.getHost(), uri.getPort(),
+                                          messageImpl.getPayloadBinaryData());
+                    }
+                    //check whether message is too big for sending
+                    if (messageSegments > MAXIMUM_MESAGE_SEGMENTS_SUPPORTED)
+                    {
+                        throw new IllegalArgumentException(
+                            "Message exceeds the maximum supported segments: " +
+                            MAXIMUM_MESAGE_SEGMENTS_SUPPORTED);
+                    }
+                    ApplicationUtils appUtils = ApplicationUtils.getInstance();
+                    // remove the protocol
+                    SMSPermissionImpl permission = new SMSPermissionImpl("sms://*",
+                            "send",messageSegments,uri.getHost()
+                            + ((uri.getPort() != null
+                                && uri.getPort().length() > 0)
+                               ? (":" + uri.getPort()) : ""));
+
+                    appUtils.checkPermission(permission);
+                    //to allow close operation to succeed while sending, send
+                    //operation is carried out in a new thread
+                    new Thread(new Runnable()
+                    {
+                        public void run()
+                        {
+                            doSend();
+                        }
+                    }).start();
+                    //wait till the message was sent successfully
+                    try
+                    {
+                        iSendLock.wait();
+                    }
+                    catch (InterruptedException ex)
+                    {
+                        Logger.ELOG(Logger.EWMA, "Sending a message failed", ex);
+                    }
+                    if (iSendResult < 0)
+                    {
+                        _checkError(iSendResult, true);
+                    }
+                }
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo, "- SMSConnectionImpl::send");
+    }
+
+
+    /**
+     * This method runs in seperate thread and unblocks the send with the
+     * send status
+     */
+    private void doSend()
+    {
+        iSendResult = _sendMessage(iNativeHandle);
+        synchronized (iSendLock)
+        {
+            iSendLock.notify();
+        }
+    }
+
+    /**
+     * Callback through which the Native notifies about the receiving of a
+     * Message or any error while receiving a message.
+     * @param1  aReceivedMesgs - number of messages to be received
+     * @param2  aError - any error while receiving a message
+     *
+     */
+    protected int messageReceiveCallback(int aReceivedMsgs, int aError)
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::messageReceiveCallback");
+        synchronized (iMessageLock)
+        {
+            //check either receive was called or a listener was set on this
+            //connection
+            if (0 == iMessageWaiting && (null == iMessageListener))
+            {
+                //if neither receive nor setMessageListener was called on this
+                //wait for either of them to be called
+                try
+                {
+                    iMessageLock.wait();
+                }
+                catch (InterruptedException ex)
+                {
+                    Logger.ELOG(Logger.EWMA, ex.toString(), ex);
+                }
+            }
+        }
+        synchronized (iReadLock)
+        {
+            //calculate number of new messages arrived and to be notified
+            if (aReceivedMsgs > 0)
+            {
+                iMessagesOnQueue += aReceivedMsgs;
+                iMessageNotificationsToMake += aReceivedMsgs;
+            }
+            //incase of any error store that error
+            else
+            {
+                iRecvError = aError;
+            }
+            if (0 != iMessageWaiting)
+            {
+                iMessageWaiting--;
+                //if a receive operation was called notify the receive about
+                //the incoming message
+                iReadLock.notify();
+            }
+            else if (null != iMessageListener)
+            {
+                //if a listener was set notify the listener about the incoming
+                //messages
+                while (iMessageNotificationsToMake > 0)
+                {
+                    iMessageListener.notifyIncomingMessage(this);
+                    iMessageNotificationsToMake--;
+                }
+                //incase of an error notify
+                if (iRecvError != 0)
+                {
+                    iMessageListener.notifyIncomingMessage(this);
+                }
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ SMSConnectionImpl::messageReceiveCallback");
+        return ((iState == CLOSED)? -1 : 0);
+    }
+
+    private native  int _createConnection(String aUri,
+                                          boolean aServerConnection);
+
+    private native void _openConnection(int aNativeHandle);
+
+    private native void _closeConnection(int aNativeHandle);
+
+    private native int _smsTextDataSegments(int aNativePeerHandle,
+                                            int aMsgType, String aHostAddress, String aHostPort, String aData);
+
+    private native int _smsBinaryDataSegments(int aNativePeerHandle,
+            int aMsgType, String aHostAddress, String aHostPort, byte[] aData);
+
+    private native int _sendMessage(int aNativePeerHandle);
+
+    private native int _getMessageType(int aNativePeerHandle);
+
+    private native String _getHostAddress(int aNativePeerHandle);
+
+    private native long _getTimestamp(int aNativePeerHandle);
+
+    private native Object _getReceivedMessageData(int aNativePeerHandle,
+            int aMsgType);
+    private native void _dispose(int aNativeHandle);
+
+    private native void _checkError(int aError,boolean aIsSendOperation);
+}
+