/*
* 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);
}