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