diff -r e8e63152f320 -r 2a9601315dfc javaextensions/wma/mms/javasrc/com/nokia/mj/impl/mms/MMSConnectionImpl.java --- /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); +} + +