javaextensions/wma/mms/javasrc/com/nokia/mj/impl/mms/MMSConnectionImpl.java
changeset 21 2a9601315dfc
child 78 71ad690e91f5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javaextensions/wma/mms/javasrc/com/nokia/mj/impl/mms/MMSConnectionImpl.java	Mon May 03 12:27:20 2010 +0300
@@ -0,0 +1,746 @@
+/*
+* 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.mms;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import javax.wireless.messaging.*;
+
+import com.nokia.mj.impl.utils.Logger;
+import com.nokia.mj.impl.mms.MmsPropertyRetriever;
+import com.nokia.mj.impl.rt.support.ShutdownListener;
+import com.nokia.mj.impl.rt.support.ApplicationUtils;
+
+public final class MMSConnectionImpl implements MessageConnection
+{
+    private static final int INITIAL = 0;
+
+    private static final int OPEN = 1;
+
+    private static final int CLOSED = 2;
+
+    // current state of execution
+    private int iState;
+
+    // status of send
+    private int iSendError = 0;
+
+    // number of received messages
+    private int iMessagesOnQueue;
+
+    // number of notifications to be done
+    private int iNotificationsToMake;
+
+    // handle to the native side
+    private int iNativeHandle;
+
+    // waiting for message
+    private boolean iMessageWaiting;
+
+    // any message notification missed
+    private boolean iMissedMessage;
+
+    // Is Server Connection
+    private boolean iServerConnection;
+
+    // synchronization for reading message
+    private final Object iReadLock;
+
+    // synchronization for sending
+    private final Object iSendLock;
+
+    // synchronization for sending message
+    private final Object iWriteLock;
+
+    // synchronization for closing
+    private final Object iCloseLock;
+
+    // synchronization for receiving
+    private final Object iMessageLock;
+
+    private String iUri;
+
+    // Application Id
+    private String iAppID = null;
+
+    // the listener used for messages receiving notifications
+    private MessageListener iMessageListener;
+
+    public MMSConnectionImpl(String aUri, boolean aServerConnection)
+    throws IOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::MMSConnectionImpl()");
+        iUri = aUri;
+        iState = INITIAL;
+        iServerConnection = aServerConnection;
+        // create the native side peer
+        iNativeHandle = _createPeer(iServerConnection, iUri);
+        iReadLock = new Object();
+        iWriteLock = new Object();
+        iCloseLock = new Object();
+        iMessageLock = new Object();
+        iSendLock = new Object();
+        // register for shutdown listening
+        setShutdownListener();
+        if (iServerConnection)
+        {
+            iAppID = aUri.substring("mms://:".length());
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::MMSConnectionImpl()");
+    }
+
+    /*
+    * This function registers this object for shutDown.
+    */
+    private void setShutdownListener()
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::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 e)
+                {
+                    //Nothing to do, just ignore
+                    Logger.LOG(Logger.EWMA, Logger.EInfo, e.toString());
+                }
+            }
+        });
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::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.
+     */
+    public void open()
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::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)
+            {
+                new Thread(new Runnable()
+                {
+                    public void run()
+                    {
+                        _openConnection(iNativeHandle);
+                    }
+                }).start();
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::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.
+     */
+    public void close() throws IOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::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,
+                   "- MMSConnectionImpl::close()");
+    }
+
+    /**
+     * Returns the number of segments required for sending this Message.
+     */
+    public int numberOfSegments(Message aMessage)
+    {
+        // For MMS numberOfSegments is always 1.
+        return 1;
+    }
+
+    private int getMMSMaxSize()
+    {
+        return MmsPropertyRetriever.getMMSMaxSize();
+    }
+
+    /**
+     * Method for creating a Multipart 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
+     *            "Multipart"
+     */
+    public Message newMessage(String aType)
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::newMessage()");
+        if (MessageConnection.MULTIPART_MESSAGE.equals(aType))
+        {
+            if (iServerConnection)
+            {
+                Logger.LOG(Logger.EWMA, Logger.EInfo,
+                           "- MMSConnectionImpl::newMessage()");
+                return new MultipartMessageImpl(null, 0, getMMSMaxSize());
+            }
+            else
+            {
+                Logger.LOG(Logger.EWMA, Logger.EInfo,
+                           "- MMSConnectionImpl::newMessage()");
+                return new MultipartMessageImpl(iUri, 0, getMMSMaxSize());
+            }
+        }
+        // unrecognized type
+        throw new IllegalArgumentException(
+            "Unrecognized message type: " + aType);
+    }
+
+    /**
+     * Method for creating a Multipart Message object
+     *
+     * @param aType
+     *            The type of the message to be created. It can only be
+     *            "Multipart"
+     * @param aAddress
+     *            the destination address of the Message
+     */
+    public Message newMessage(String aType, String aAddress)
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::newMessage()");
+        if (MessageConnection.MULTIPART_MESSAGE.equals(aType))
+        {
+            Logger.LOG(Logger.EWMA, Logger.EInfo,
+                       "- MMSConnectionImpl::newMessage()");
+            return new MultipartMessageImpl(aAddress, 0, getMMSMaxSize());
+        }
+        // unrecognized type
+        throw new IllegalArgumentException(
+            "Unrecognized message type " + aType);
+
+    }
+
+    /**
+     * Method for sending a certain message. Only Multipart Messages can be sent
+     * through this connection. The Message to sent is validated before sending.
+     *
+     * @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 any of the header field
+     *             is not specified
+     */
+    public void send(Message aMsg) throws IOException, InterruptedIOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::send()");
+        // validate the message to be sent
+        MultipartMessageImpl mMsg = validateMessage(aMsg);
+        if (iServerConnection)
+        {
+            mMsg.setReplyToAppID(iAppID);
+        }
+        else
+        {
+            // validate the client-side message
+            validateClientMessage(mMsg);
+        }
+        // Before sending the message serialize it
+        byte[] bMsg = mMsg.serialize();
+        // to synchronize send functionality
+        synchronized (iWriteLock)
+        {
+            synchronized (iSendLock)
+            {
+                // send operation should not be carried out while close is in
+                // progress
+                synchronized (iCloseLock)
+                {
+                    if (iState == CLOSED)
+                    {
+                        throw new IOException("Sending message failed: " +
+                                              ExceptionStrings.CONNECTION_CLOSED);
+                    }
+                    //checking for valid permissions
+                    ApplicationUtils appUtils = ApplicationUtils.getInstance();
+                    MMSPermissionImpl permission = new MMSPermissionImpl(
+                        "mms://*", "send",
+                        getRecipientsCount(mMsg));
+                    appUtils.checkPermission(permission);
+                    // send the message
+                    int status = _send(bMsg, 0, bMsg.length, mMsg.getAddress(),
+                                       iNativeHandle);
+                    if (status < 0)
+                    {
+                        _checkError(status);
+                    }
+                    // wait till the message was sent successfully
+                    try
+                    {
+                        iSendLock.wait();
+                    }
+                    catch (InterruptedException e)
+                    {
+                        Logger.ELOG(Logger.EWMA, "Message sending failed", e);
+                    }
+                    if (iSendError < 0)
+                    {
+                        _checkError(iSendError);
+                    }
+                }
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::send()");
+    }
+
+    /**
+     * Callback through which the Native notifies about the sending of a Message
+     * or any error while sending a message
+     *
+     * @param1 result  the status of send operation
+     */
+    protected void messageSentCallback(int aResult)
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::messageSentCallback()");
+        synchronized (iSendLock)
+        {
+            iSendError = aResult;
+            iSendLock.notify();
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::messageSentCallback()");
+    }
+
+    /**
+     * Receive is a 3 step process: 1) Wait for some incoming message 2)
+     * Retrieve the the message 3) Construct a Multipart Message
+     *
+     * @exceptions throws IOException if the connection is already closed.
+     * @exceptions throws InterruptedIOException if the connection is closed
+     *             while receiving
+     * @returns the constructed message
+     */
+    synchronized public Message receive() throws IOException,
+                InterruptedIOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::receive()");
+        if (!iServerConnection)
+        {
+            // Not Supported in Client MODE
+            throw new IOException(ExceptionStrings.CLIENT_MODE);
+        }
+        Message rMsg;
+        synchronized (iReadLock)
+        {
+            // check for connection
+            if (iState == CLOSED)
+            {
+                throw new IOException("Receiving message failed: " +
+                                      ExceptionStrings.CONNECTION_CLOSED);
+            }
+            // if no messages were there to receive wait till some message
+            // arrives
+            if (iMessagesOnQueue == 0)
+            {
+                iMessageWaiting =true;
+                // notify the notifier thread
+                synchronized (iMessageLock)
+                {
+                    iMessageLock.notify();
+                }
+                // wait for incoming message notification
+                try
+                {
+                    iReadLock.wait();
+                }
+                catch (InterruptedException e)
+                {
+                    Logger.ELOG(Logger.EWMA, "Message receiving failed", e);
+                    throw new InterruptedIOException("");
+                }
+            }
+            synchronized (iCloseLock)
+            {
+                //if the connection was closed throw InterruptedIOException
+                if (iState == CLOSED)
+                {
+                    throw new InterruptedIOException(
+                        "Connection closed while receiving message");
+                }
+                //checking for valid permissions
+                ApplicationUtils appUtils = ApplicationUtils.getInstance();
+                MMSPermissionImpl permission = new MMSPermissionImpl("mms://*",
+                        "receive");
+                appUtils.checkPermission(permission);
+
+                byte[] msg = _retrieveMessage(iNativeHandle);
+                rMsg = MultipartMessageImpl.deserialize(msg, getMMSMaxSize());
+                iMessagesOnQueue--;
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::receive()");
+        return rMsg;
+    }
+
+    /**
+     * 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,
+                   "+ MMSConnectionImpl::setMessageListener()");
+        if (!iServerConnection)
+        {
+            throw new IOException(ExceptionStrings.CLIENT_MODE);
+        }
+        if (iState == CLOSED)
+        {
+            throw new IOException("Setting message listener failed: " +
+                                  ExceptionStrings.CONNECTION_CLOSED);
+        }
+        iMessageListener = aListener;
+        if (iMessageListener != null)
+        {
+            synchronized (iMessageLock)
+            {
+                iMessageLock.notify();
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::setMessageListener()");
+    }
+
+    /**
+     * Method that validates the message attributes
+     *
+     * @exceptions Throws IOException if any of the attribute is not valid
+     */
+    private MultipartMessageImpl validateMessage(Message aMsg)
+    throws IOException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::validateMessage()");
+        // check against null
+        if (null == aMsg)
+        {
+            throw new NullPointerException(ExceptionStrings.NULL_MESSAGE);
+        }
+
+        // make sure it is a MultipartMessageImpl
+        MultipartMessageImpl mMsg;
+        try
+        {
+            mMsg = (MultipartMessageImpl) aMsg;
+        }
+        catch (ClassCastException ex)
+        {
+            throw new IllegalArgumentException(ExceptionStrings.
+                                               INVALID_MESSAGE_TYPE);
+        }
+
+        // check basic validation of the message and the corresponding parts
+        if (!mMsg.iValidationDone)
+        {
+            // Forces the validation of the message if it hasn't been done
+            // validation is done so that we reconstruct the message
+            // with the eyes of the application and report any exceptions
+            // to the application
+            MultipartMessageImpl cloneMsg = new MultipartMessageImpl(
+                mMsg.getDestinationAddress(),
+                mMsg.getTimestamp() == null ? 0 : mMsg.getTimestamp().getTime(),
+                MmsPropertyRetriever.getMMSMaxSize());
+
+            // first go through addresses and clone them for the new message
+            cloneAddresses(MultipartMessageHeader.ADDRESS_TYPE_TO, cloneMsg,
+                           mMsg);
+            cloneAddresses(MultipartMessageHeader.ADDRESS_TYPE_BCC, cloneMsg,
+                           mMsg);
+            cloneAddresses(MultipartMessageHeader.ADDRESS_TYPE_CC, cloneMsg,
+                           mMsg);
+
+            // clone the headers
+            cloneMsg.setHeader(MultipartMessageHeader.DELIVERY_TIME_HEADER,
+                               mMsg.getHeader(MultipartMessageHeader.DELIVERY_TIME_HEADER));
+            cloneMsg.setHeader(MultipartMessageHeader.PRIORITY_HEADER,
+                               mMsg.getHeader(MultipartMessageHeader.PRIORITY_HEADER));
+
+            // clone the message parts
+            cloneMessageParts(cloneMsg, mMsg);
+
+            // clone the startContentId
+            cloneMsg.setStartContentId(mMsg.getStartContentId());
+
+            // clone the subject
+            cloneMsg.setSubject(mMsg.getSubject());
+        }
+
+        // make sure there are recipients
+        if (isEmpty(mMsg.getAddresses(MultipartMessageHeader.ADDRESS_TYPE_TO))
+                && isEmpty(mMsg.getAddresses(MultipartMessageHeader.ADDRESS_TYPE_BCC))
+                && isEmpty(mMsg.getAddresses(MultipartMessageHeader.ADDRESS_TYPE_CC)))
+        {
+            throw new IllegalArgumentException(ExceptionStrings.NO_RECIPIENTS);
+        }
+
+        if (mMsg.iReply)
+        {
+            // reset the delivery time
+            mMsg.setHeader(MultipartMessageHeader.DELIVERY_TIME_HEADER, null);
+            // if this message does not have the replyToAppid, it means it came
+            // from a client connection and we can not reply
+            if (mMsg.getReplyToAppID() == null ||(mMsg.getReplyToAppID() != null
+                                                  && (mMsg.getReplyToAppID().length() == 0 || mMsg.getReplyToAppID()
+                                                      .equals(MultipartMessageHeader.DUMMY_REPLY_TO_APP_ID))))
+            {
+                throw new IllegalArgumentException(ExceptionStrings.
+                                                   CLIENT_MESSAGE);
+            }
+            String dest = mMsg.getAddress();
+            mMsg.removeAddresses();
+            // replace applicationID with replyToAppID
+            mMsg.setAddress(dest);
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::validateMessage()");
+        return mMsg;
+    }
+
+    private void cloneAddresses(String aAddressType,
+                                MultipartMessageImpl aDestMsg, MultipartMessageImpl aSourceMsg)
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::cloneAddresses()");
+        String[] addresses = aSourceMsg.getAddresses(aAddressType);
+        if (addresses != null)
+        {
+            for (int i = 0; i < addresses.length; i++)
+            {
+                aDestMsg.addAddress(aAddressType, addresses[i]);
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::cloneAddresses()");
+    }
+
+    private void cloneMessageParts(MultipartMessageImpl aDestMsg,
+                                   MultipartMessageImpl aSourceMsg) throws SizeExceededException
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::cloneMessageParts()");
+        MessagePart[] parts = aSourceMsg.getMessageParts();
+        if (parts != null)
+        {
+            for (int i = 0; i < parts.length; i++)
+            {
+                MessagePart part = parts[i];
+                aDestMsg.addMessagePart(new MessagePart(part.getContent(),
+                                                        part.getMIMEType(), part.getContentID(),
+                                                        part.getContentLocation(), part.getEncoding()));
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::cloneMessageParts()");
+    }
+
+    /**
+     * Check that the address that has been provided when this connection was
+     * created is still among the "to" recipients
+     */
+    private void validateClientMessage(MultipartMessageImpl aMsg)
+    {
+        // sets the replyToAppID to its own iAppID
+        aMsg.setReplyToAppID(MultipartMessageHeader.DUMMY_REPLY_TO_APP_ID);
+    }
+
+    /**
+     * Method used for checking if there are no recipients set for * a message
+     * to be set
+     */
+    private boolean isEmpty(String[] aArray)
+    {
+        if (aArray == null || (aArray != null && aArray.length == 0))
+        {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Method that ensures whether a connection is opened
+     *
+     * @exceptions Throws IOException if the connection is in closed state
+     */
+    private void ensureOpen() throws IOException
+    {
+        synchronized (iCloseLock)
+        {
+            if (iState == CLOSED)
+            {
+                throw new IOException(ExceptionStrings.CONNECTION_CLOSED);
+            }
+        }
+    }
+
+    /**
+     * 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
+     *
+     */
+    protected int messageReceiveCallback(int aReceivedMesgs)
+    {
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "+ MMSConnectionImpl::messageReceiveCallback()");
+        synchronized (iMessageLock)
+        {
+            if ((!iMessageWaiting) && (null == iMessageListener))
+            {
+                try
+                {
+                    iMessageLock.wait();
+                }
+                catch (InterruptedException e)
+                {
+                    Logger.ELOG(Logger.EWMA, "Receiving a callback failed", e);
+                }
+            }
+        }
+        synchronized (iReadLock)
+        {
+            if (aReceivedMesgs >= 0)
+            {
+                iMessagesOnQueue += aReceivedMesgs;
+                iNotificationsToMake += aReceivedMesgs;
+            }
+            if (iMessageWaiting)
+            {
+                iMessageWaiting = false;
+                //if a receive operation was called notify the receive about
+                //the incoming message
+                iReadLock.notify();
+
+            }
+            else if (iMessageListener != null)
+            {
+                while (iNotificationsToMake > 0)
+                {
+                    iMessageListener.notifyIncomingMessage(this);
+                    iNotificationsToMake--;
+                }
+            }
+        }
+        Logger.LOG(Logger.EWMA, Logger.EInfo,
+                   "- MMSConnectionImpl::messageReceiveCallback()");
+        return ((iState == CLOSED)? -1 : 0);
+    }
+
+    private int getRecipientsCount(MultipartMessageImpl mMsg)
+    {
+        int cnt = 0;
+        String[] tmp = mMsg.getAddresses(
+                           MultipartMessageHeader.ADDRESS_TYPE_TO);
+        if (tmp != null)
+        {
+            cnt += tmp.length;
+        }
+        tmp = mMsg.getAddresses(
+                  MultipartMessageHeader.ADDRESS_TYPE_CC);
+        if (tmp != null)
+        {
+            cnt += tmp.length;
+        }
+        tmp = mMsg.getAddresses(
+                  MultipartMessageHeader.ADDRESS_TYPE_BCC);
+        if (tmp != null)
+        {
+            cnt += tmp.length;
+        }
+        return cnt;
+    }
+
+    private native int _createPeer(boolean aServerConnection, String aUri);
+
+    private native void _openConnection(int aNativeHandle);
+
+    private native int _send(byte[] aMsg, int aOffset, int aMsgLength,
+                             String aAddress, int aNativeHandle);
+
+    private native byte[] _retrieveMessage(int aNativeHandle);
+
+    private native void _closeConnection(int aNativeHandle);
+
+    private native void _dispose(int aNativeHandle);
+
+    private native void _checkError(int aError);
+}
+
+