/*
* Copyright (c) 2008-2010 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 java.lang.ref.WeakReference;
import com.nokia.mj.impl.utils.Logger;
import com.nokia.mj.impl.gcf.PushValidator;
import com.nokia.mj.impl.rt.support.Finalizer;
import com.nokia.mj.impl.mms.MmsPropertyRetriever;
import com.nokia.mj.impl.rt.support.ShutdownListener;
import com.nokia.mj.impl.rt.support.ApplicationInfo;
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;
private Finalizer iFinalizer;
private ShutdownListener iShutDownListener;
// 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;
if (iServerConnection)
{
// Check if this url is push registered by other application
// Get the insatnce of ApplicationInfo.
ApplicationInfo appInfo = ApplicationInfo.getInstance();
if (PushValidator.isRegisteredPushUriStartingWith(aUri,
appInfo.getSuiteUid(), PushValidator.RUNTIME_COMMS))
{
throw new IOException("Connection already exists");
}
iAppID = aUri.substring("mms://:".length());
}
// 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
iShutDownListener = new MmsShutDownListener(this);
// register for finalization
iFinalizer = registerForFinalization();
Logger.LOG(Logger.EWMA, Logger.EInfo,
"- MMSConnectionImpl::MMSConnectionImpl()");
}
/*
* This function registers this object for Finalization.
*/
public Finalizer registerForFinalization()
{
return new Finalizer()
{
public void finalizeImpl()
{
try
{
close();
}
catch (IOException e)
{
//Nothing to do, just ignore
Logger.LOG(Logger.EWMA, Logger.EInfo, e.toString());
}
}
};
}
/*
* Registering for shutDown Listener.
*/
private static class MmsShutDownListener implements ShutdownListener
{
private final WeakReference iImpl;
private MmsShutDownListener(MMSConnectionImpl impl)
{
// Get the instance of ApplicationUtils.
ApplicationUtils appUtils = ApplicationUtils.getInstance();
// Add the listener.
appUtils.addShutdownListener(this);
// Create weak reference to impl object.
iImpl = new WeakReference(impl);
}
public void shuttingDown()
{
// Get a strong reference to impl class if it is not yet GC'ed.
MMSConnectionImpl impl = (MMSConnectionImpl)iImpl.get();
if (impl != null)
{
try
{
impl.close();
}
catch (IOException e)
{
//Nothing to do, just ignore
Logger.LOG(Logger.EWMA, Logger.EInfo, e.toString());
}
}
}
}
/**
* 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);
// Remove the shutdown listener.
ApplicationUtils.getInstance().removeShutdownListener(
iShutDownListener);
}
}
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);
}