001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.tck.wma.mms;
028:
029: import com.sun.tck.wma.PropLoader;
030: import com.sun.tck.wma.BinaryMessage;
031: import com.sun.tck.wma.Message;
032: import com.sun.tck.wma.MessageConnection;
033: import com.sun.tck.wma.MessageTransportConstants;
034: import com.sun.tck.wma.TextMessage;
035: import com.sun.tck.wma.MultipartMessage;
036: import com.sun.tck.wma.sms.BinaryObject;
037: import com.sun.tck.wma.sms.MessagePacket;
038: import com.sun.tck.wma.sms.TextObject;
039: import com.sun.midp.io.j2me.sms.TextEncoder;
040: import java.net.DatagramPacket;
041: import java.net.DatagramSocket;
042: import java.net.InetAddress;
043: import java.util.Properties;
044: import java.util.Vector;
045:
046: import java.io.IOException;
047:
048: /**
049: * MMS message connection handler.
050: */
051: public class MMSMessageConnection extends PropLoader implements
052: MessageConnection {
053:
054: /** Machine name - the parsed target address from the URL. */
055: protected String host = null;
056:
057: /** Application ID - the parsed ID from the URL. */
058: protected String appID = null;
059:
060: /** Datagram host for sending/receiving. */
061: protected String clientHost;
062:
063: /** Datagram transport for sending. */
064: protected int portOut;
065:
066: /** Datagram transport for receiving. */
067: protected int portIn;
068:
069: /** Phone number of the message sender. */
070: protected String phoneNumber;
071:
072: /** The application ID to which replies should be sent. */
073: protected String replyToAppID;
074:
075: /** Datagram server connection. */
076: DatagramSocket dgc;
077:
078: /** Datagram buffer. */
079: byte[] buf = new byte[MessageTransportConstants.DATAGRAM_PACKET_SIZE];
080:
081: /** Datagram envelope for sending or receiving messages. */
082: DatagramPacket mess = new DatagramPacket(buf,
083: MessageTransportConstants.DATAGRAM_PACKET_SIZE);
084:
085: /**
086: * The "open" flag indicates when the connection is open. When the
087: * connection is closed, subsequent operations throw an exception.
088: */
089: protected boolean open;
090:
091: /**
092: * Construct a new MMS message connection handler.
093: */
094: public MMSMessageConnection() {
095:
096: /*
097: * Configurable parameters for low level transport.
098: * e.g.: mms://+5551234:33300 maps to datagram://129.148.70.80:123
099: */
100:
101: // Default values:
102: clientHost = "localhost";
103: portOut = 33300;
104: portIn = 33301;
105: phoneNumber = "+5551234";
106: replyToAppID = "com.sun.mms.MMSTest";
107:
108: /*
109: * Check for overrides in the "connections.prop" configuration file.
110: */
111:
112: clientHost = getProp("localhost", "JSR_205_DATAGRAM_HOST",
113: "connections.prop", "DatagramHost");
114:
115: portOut = getIntProp(33300, "JSR_205_MMS_OUT_PORT",
116: "connections.prop", "MMSDatagramPortOut");
117:
118: portIn = getIntProp(33301, "JSR_205_MMS_PORT",
119: "connections.prop", "MMSDatagramPortIn");
120:
121: // Sender (This connection)'s phone number.
122: phoneNumber = getProp("+5551234", "JSR_205_PHONE_NUMBER",
123: "connections.prop", "PhoneNumber");
124:
125: // Sender (This connection)'s application ID.
126: replyToAppID = getProp("com.sun.mms.MMSTest",
127: "JSR_205_MMS_REPLY_TO_ID", "connections.prop",
128: "MMSReplyToAppID");
129:
130: }
131:
132: /**
133: * Opens a connection. This method is called from
134: * <code>Connector.open()</code> method to obtain the destination address
135: * given in the <code>name</code> parameter.
136: * <p>
137: * The format for the <code>name</code> string for this method is:
138: * <code>mms://<em>phone_number</em>:<em>port</em></code> where the
139: * <em>phone_number:</em> is optional. If the <em>phone_number</em>
140: * parameter is present, the connection is being opened in "client" mode.
141: * This means that messages can be sent. If the parameter is absent, the
142: * connection is being opened in "server" mode. This means that messages
143: * can be sent and received.
144: * <p>
145: * The connection is opened to any of the following, low-level transport
146: * mechanisms:
147: * <ul>
148: * <li>A datagram Short Message Peer to Peer (SMPP) to a service center.
149: * <li>A <code>comm</code> connection to a phone device with AT-commands.
150: * <li>a native MMS stack.
151: * </ul>
152: *
153: * @param name the target of the connection
154: * @return this connection
155: * @throws IOException if the connection is closed or unavailable.
156: */
157: public MessageConnection openPrim(String name) throws IOException {
158:
159: // Invoke the implementation-specific handler.
160: return openPrimInternal(name);
161: }
162:
163: /**
164: * Opens a connection. This method is called from
165: * <code>Connector.open()</code> method to obtain the destination address
166: * given in the <code>name</code> parameter.
167: * <p>
168: * The format for the <code>name</code> string for this method is:
169: * <code>mms://<em>phone_number</em>:<em>appID</em></code> where the
170: * <em>phone_number:</em> is optional. If the <em>phone_number</em>
171: * parameter is present, the connection is being opened in "client" mode.
172: * This means that messages can be sent. If the parameter is absent, the
173: * connection is being opened in "server" mode. This means that messages
174: * can be sent and received.
175: * <p>
176: * The connection is opened to any of the following, low-level transport
177: * mechanisms:
178: * <ul>
179: * <li>A datagram Short Message Peer to Peer (SMPP) to a service center.
180: * <li>A <code>comm</code> connection to a phone device with AT-commands.
181: * <li>a native MMS stack.
182: * </ul>
183: *
184: * @param name the target of the connection
185: * @return this connection
186: * @throws IOException if the connection is closed or unavailable.
187: */
188: public MessageConnection openPrimInternal(String name)
189: throws IOException {
190:
191: /*
192: * The general form of a MMS address is <code>mms://host:appID</code>.
193: * The form at this point should now be <code>//host:appID</code>
194: */
195: if ((name == null) || (name.length() <= 2)
196: || (name.charAt(0) != '/') || (name.charAt(1) != '/')) {
197:
198: throw new IllegalArgumentException(
199: "Missing protocol separator.");
200: }
201:
202: String fullAddress = "mms:" + name;
203:
204: MMSAddress parsedAddress = MMSAddress
205: .getParsedMMSAddress(fullAddress);
206: if (parsedAddress == null) {
207: throw new IllegalArgumentException(
208: "Invalid MMS connection URL");
209: }
210:
211: // Pick up the phone number to which the message will be sent.
212: host = null;
213: if (parsedAddress.address != null) {
214: host = new String(parsedAddress.address);
215: }
216:
217: // Pick up the application ID to which the message will be sent.
218: appID = null;
219: if (parsedAddress.appId != null) {
220: appID = new String(parsedAddress.appId);
221: }
222:
223: // Open the inbound server datagram connection. The appID is not used.
224: try {
225: dgc = open0(appID);
226: } catch (IOException ioe) {
227: throw new IOException("Unable to open MMS connection.");
228: }
229: open = true;
230:
231: // Return this open connection.
232: return this ;
233: }
234:
235: /**
236: * Open the transport-layer-level connection.
237: *
238: * @param appID The application ID (Unused in this implementation).
239: * @return The open datagram socket (Transport mechanism).
240: */
241: private DatagramSocket open0(String appID) throws IOException {
242:
243: return new DatagramSocket(portIn);
244: }
245:
246: /**
247: * Constructs a new message object of <code>MULTIPART_MESSAGE</code> type.
248: * <p>
249: * If this method is called in a sending mode, a new
250: * <code>Message</code> object is requested from the connection. Example:
251: * <p>
252: * <code>Message msg = conn.newMessage(MULTIPART_MESSAGE);</code>
253: * <p>
254: * The created <code>Message</code> does not have the destination
255: * address set. It must be set by the application before
256: * the message is sent.
257: * <p>
258: * If it is called in receiving mode, the <code>Message</code> object does
259: * have its address set. The application can act on the object to extract
260: * the address and message data.
261: * <p>
262: * <!-- The <code>type</code> parameter indicates the number of bytes
263: * that should be
264: * allocated for the message. No restrictions are placed on the application
265: * for the value of <code>size</code>.
266: * A value of <code>null</code> is permitted and creates a
267: * <code>Message</code> object
268: * with a 0-length message. -->
269: *
270: * @param type <code>MULTIPART_MESSAGE</code> is the only type permitted.
271: * @return A new <code>Message</code> object.
272: */
273: public Message newMessage(String type) {
274: return newMessage(type, null);
275: }
276:
277: /**
278: * Constructs a new <code>MULTIPART_MESSAGE</code> message object with the
279: * desired a destination address.
280: * <p>
281: * <p>
282: * The destination address <code>addr</code> has the following format:
283: * <code>sms://<em>phone_number</em>:<em>port</em></code>.
284: *
285: * @param type <code>MULTIPART_MESSAGE</code> is the only type permitted.
286: * @param addr The destination address of the message.
287: * @return A new <code>Message</code> object.
288: */
289: public Message newMessage(String type, String addr) {
290:
291: // Return the appropriate type of sub-message.
292: if (!(type == MessageConnection.MULTIPART_MESSAGE)) {
293: throw new IllegalArgumentException(
294: "Message type not supported.");
295: }
296:
297: return new MultipartObject(addr);
298: }
299:
300: /**
301: * Sends an MMS message.
302: *
303: * @param msg The MMS <code>Message</code> to be sent.
304: * @exception ConnectionNotFoundException if the address is invalid or if
305: * no address is found in the message.
306: * @exception IOException if an I/O error occurs.
307: */
308: public void send(Message msg) throws IOException {
309:
310: if (msg == null) {
311: throw new NullPointerException("Null message.");
312: }
313:
314: if (!(msg instanceof MultipartMessage)) {
315: throw new IllegalArgumentException(
316: "Unsupported message type.");
317: }
318:
319: // Create the multi-part object that will be used below.
320: MultipartObject mpo = (MultipartObject) msg;
321:
322: /*
323: * Check for valid MMS URL connection format. Note that the addresses in
324: * the lists are not used. This is simply a check to make sure that the
325: * addresses can be placed into the multipart object's header when the
326: * header and message are bundled within MultipartObject.
327: *
328: * Process each MMS address in the to:, cc: and bcc: address lists. An
329: * MMS address assumes this form: address:appID
330: *
331: * Each MMS address is parsed to extract the address and application ID
332: * data. Those parts are then checked for validity.
333: *
334: * The loop starts by processing all addresses in the to: field (if
335: * any), followed by the addresses in the cc: list, then the bcc: list.
336: *
337: */
338: Vector allAddresses = new Vector();
339: String[] addresses = mpo.getAddresses("to");
340: int currIndex = 0;
341: boolean checkedTo = false;
342: boolean checkedCC = false;
343:
344: // The application ID extracted from an address in the address list.
345: String parsedAppID = null;
346: while (true) {
347:
348: /*
349: * If no addresses were in the to: field, or if all addresses have
350: * been extracted and checked from the current address list
351: * (Initially, the to: list), then continue to process the cc: list
352: * (if any), next, followed by the bcc: list.
353: */
354: if (addresses == null || currIndex >= addresses.length) {
355:
356: if (!checkedTo) {
357:
358: // The to: list has been processed. Process cc: list, next.
359: checkedTo = true;
360: addresses = mpo.getAddresses("cc");
361: currIndex = 0;
362: continue;
363:
364: } else if (!checkedCC) {
365:
366: // The cc: list has been processed. Process bcc: list, next.
367: checkedCC = true;
368: addresses = mpo.getAddresses("bcc");
369: currIndex = 0;
370: continue;
371: } else {
372:
373: /*
374: * The to:, cc: and bcc: lists have now been checked, so
375: * bail out of the while() loop.
376: */
377: break;
378: }
379: }
380:
381: /*
382: * Pick up the next address and add it to the list. Then, parse it
383: * to extract the address and application ID parts.
384: */
385: String addr = addresses[currIndex++];
386: allAddresses.addElement(addr);
387:
388: MMSAddress parsedAddress = MMSAddress
389: .getParsedMMSAddress(addr);
390:
391: if (parsedAddress == null
392: || parsedAddress.type == MMSAddress.INVALID_ADDRESS
393: || parsedAddress.type == MMSAddress.APP_ID) {
394: throw new IllegalArgumentException(
395: "Invalid MMS address: " + addr);
396: }
397:
398: if (parsedAppID == null) {
399: parsedAppID = parsedAddress.appId;
400: } else if (parsedAddress.appId != null
401: && !parsedAppID.equals(parsedAddress.appId)) {
402: throw new IllegalArgumentException(
403: "Only one Application-ID "
404: + "can be specified per message");
405: }
406:
407: } // while
408:
409: if (allAddresses.size() == 0) {
410: throw new IllegalArgumentException(
411: "No to, cc, or bcc addresses");
412: }
413:
414: // Construct the target address protocol string.
415: String messageAppID = mpo.getApplicationID();
416: String toAddress = "mms://:";
417: if (messageAppID != null) {
418: toAddress = toAddress + messageAppID;
419: }
420:
421: /*
422: * If no application ID was supplied, use the ID that was used to open
423: * the connection as the default ID.
424: */
425: if (messageAppID != null && host == null) {
426: mpo.setReplyToApplicationID(replyToAppID);
427: }
428:
429: // Preserve the original "from" address.
430: String oldFromAddress = ((MultipartObject) mpo).getAddress();
431:
432: // Establish the return address.
433: String fromAddress = "mms://" + phoneNumber;
434: if (replyToAppID != null) {
435: fromAddress = fromAddress + ":" + replyToAppID;
436: }
437: mpo.setFromAddress(fromAddress);
438:
439: // Send the message and reply information.
440: byte[] header = mpo.getHeaderAsByteArray();
441: byte[] body = mpo.getBodyAsByteArray();
442: int status = send0(dgc, toAddress, fromAddress, messageAppID,
443: replyToAppID, header, body);
444:
445: // Restore the "from" address.
446: mpo.setFromAddress(oldFromAddress);
447: }
448:
449: /**
450: * Sends the MMS message (Transport-layer-specific code).
451: *
452: * @param ds The transport-layer-specific datagram socket connection.
453: * @param toAddress The recipient's MMS address.
454: * @param fromAddress The sender's MMS address.
455: * @param appID The application ID to be matched against incoming messages.
456: * @param replyToAppID The ID of the application that processes replies.
457: * @param mmsHeader The message header context.
458: * @param mmsBody The message body context.
459: *
460: * @return Unused status. Always <code>0</code>.
461: */
462: private int send0(DatagramSocket ds, String toAddress,
463: String fromAddress, String appID, String replyToAppID,
464: byte[] mmsHeader, byte[] mmsBody) throws IOException {
465:
466: // Combine the header and body parts.
467: int headerLen = mmsHeader.length;
468: int bodyLen = mmsBody.length;
469: byte[] msg = new byte[headerLen + bodyLen];
470: System.arraycopy(mmsHeader, 0, msg, 0, headerLen);
471: System.arraycopy(mmsBody, 0, msg, headerLen, bodyLen);
472:
473: // Use MessagePacket to stream low-endian-formatted values.
474: MessagePacket stream = new MessagePacket();
475: stream.putString(fromAddress);
476: stream.putString(appID);
477: stream.putString(replyToAppID);
478: stream.putInt(msg.length);
479: stream.putBytes(msg);
480: byte[] buffer = stream.getData();
481:
482: /*
483: * Write the message as a series of datagram packets.
484: */
485: int PACKET_MAX_SIZE =
486: // overhead = three shorts + one int.
487: MessageTransportConstants.DATAGRAM_PACKET_SIZE - 10;
488: short packetNumber;
489: short totalPackets;
490: int offset = 0; // offset into buffer.
491: int count = 0;
492:
493: // The total number of bytes to send.
494: int length = buffer.length;
495:
496: // The total number of packets to send.
497: totalPackets = (short) ((buffer.length + PACKET_MAX_SIZE - 1) / PACKET_MAX_SIZE);
498:
499: // Fragment the data buffer into multiple segments.
500: for (packetNumber = 1; packetNumber <= totalPackets; packetNumber++) {
501:
502: // Datagram envelope for sending messages.
503: mess = new DatagramPacket(buf,
504: MessageTransportConstants.DATAGRAM_PACKET_SIZE);
505: mess.setAddress(InetAddress.getByName(clientHost));
506: mess.setPort(portOut);
507:
508: // Compute the number of bytes that can be sent in this packet.
509: count = length;
510: if (count > PACKET_MAX_SIZE) {
511: count = PACKET_MAX_SIZE;
512: }
513:
514: MessagePacket mmsPacket = new MessagePacket();
515: mmsPacket.putShort(packetNumber);
516: mmsPacket.putShort(totalPackets);
517: mmsPacket.putShort((short) count);
518: mmsPacket.putInt(length);
519:
520: // Now set the payload.
521: byte[] buf = new byte[count];
522: System.arraycopy(buffer, offset, buf, 0, count);
523: mmsPacket.putBytes(buf);
524:
525: byte[] buff = mmsPacket.getData();
526: mess.setData(buff, 0, buff.length);
527: dgc.send(mess);
528:
529: // Move the pointer past the bytes and send the next packet.
530: offset += count;
531: length -= count;
532: }
533:
534: return 0;
535: }
536:
537: /**
538: * Receives the bytes that have been sent over the connection,
539: * constructs a <code>Message</code> object, and returns it.
540: * <p>
541: * If there are no <code>Message</code>s waiting on the connection,
542: * this method will block until a message
543: * is received, or the <code>MessageConnection</code> is closed.
544: *
545: * @return A <code>Message</code> object
546: * @throws IOException if an I/O error occurs.
547: */
548: public synchronized Message receive() throws IOException {
549:
550: // Call the implementation-specific code to get a message byte[]
551: byte[] buffer = receive0(dgc);
552:
553: // Decode the buffer contents to extract the proper parameters.
554: MessagePacket mmsPacket = new MessagePacket(buffer);
555: String fromAddress = mmsPacket.getString();
556: String appID = mmsPacket.getString();
557: String replyToAppID = mmsPacket.getString();
558: int msgLen = mmsPacket.getInt();
559: byte[] message = mmsPacket.getBytes(msgLen);
560:
561: // Convert the data into a multipart object and return that message.
562: // MultipartObject mpo = MultipartObject.createFromByteArray(message);
563: Message msg = MultipartObject.createFromByteArray(message);
564:
565: // IMPL_NOTE: FIX
566: // IMPL_NOTE: msg.setTimeStamp(tm.getTimeStamp());
567:
568: ((MultipartObject) msg).setFromAddress(fromAddress);
569: ((MultipartObject) msg).fixupReceivedMessageAddresses(
570: fromAddress, phoneNumber);
571:
572: return msg;
573: }
574:
575: /**
576: * Internal implementation of the message-receive code. As packets are
577: * received, they are passed off to a routine that assembles the data
578: * into a large byte array.
579: *
580: * @param ds The open datagram socket.
581: * @return A <code>Message</code> object.
582: * @throws IOException if an I/O error occurs.
583: */
584: private byte[] receive0(DatagramSocket ds) throws IOException {
585:
586: while (true) {
587:
588: // Wait for a datagram to arrive.
589: ds.receive(mess);
590:
591: // Assemble the datagram contents.
592: if (assembleFrags(mess) == true) {
593: /*
594: * When all datagrams have been received, so break out to allow
595: * the data to be processed.
596: */
597: break;
598: }
599: }
600:
601: return mmsBuffer;
602: }
603:
604: /**
605: * Closes the connection. Reset the connection-is-open flag so methods can
606: * be checked to throws an appropriate exception for operations on a closed
607: * connection.
608: *
609: * @exception IOException if an I/O error occurs.
610: */
611: public void close() throws IOException {
612:
613: if (open) {
614: dgc.close();
615: dgc = null;
616: open = false;
617: }
618: }
619:
620: /** The current packet number. */
621: private int packetNumber = 1;
622:
623: /** The offset into the assembly buffer. */
624: private int mmsOffset = 0;
625:
626: /** The assembly buffer. */
627: private byte[] mmsBuffer = null;
628:
629: /**
630: * Analyze the special datagram payload, extract the data and append the
631: * data to the main byte buffer.
632: * <p>
633: * This particular implementation assumes that the network has short hops
634: * and that datagrams arrive in the order in which they were received.
635: * Whenever datagrams arrive out of order, the assembly of packets is
636: * cancelled. This simple rule avoids the use of more complex packet
637: * assembly algorithms, allowing datagrams to be used instead of sockets.
638: * <p>
639: * The packet header has this format:
640: * <p><pre>
641: * +--------+--------------+-----------------+-----------------+
642: * | Packet | Total number | Total number of | Total number of |
643: * | number | of packets | bytes in packet | bytes in stream |
644: * +--------+--------------+-----------------+-----------------+
645: * 2 bytes 2 bytes 2 bytes 4 bytes</pre>
646: * <p>
647: * Data are stored in low-endian format. The packet data immediately
648: * follow the header.
649: *
650: * @param packet The special datagram payload.
651: * @return <code>true</code> when the last packet has been received and
652: * processed. <code>false</code> when there are more packets that are
653: * expected to be processed.
654: */
655: private boolean assembleFrags(DatagramPacket packet) {
656:
657: // Extract the packet header contents and data.
658: MessagePacket mmsPacket = new MessagePacket(packet.getData());
659: short packNum = mmsPacket.getShort();
660: short totalPackets = mmsPacket.getShort();
661: short count = mmsPacket.getShort();
662: int totalLen = mmsPacket.getInt();
663: byte[] data = mmsPacket.getBytes(count);
664:
665: if (packNum != packetNumber) {
666: /*
667: * Mismatch in packet number. Packets have
668: * either arrived out of order or a packet
669: * has been dropped.
670: */
671: System.err
672: .println("ERROR: Datagram packets have been dropped.");
673: if (mmsBuffer != null) {
674: mmsBuffer = null;
675: }
676: return false;
677: }
678:
679: /*
680: * If this is the first packet, initialize the total size of the
681: * assembly buffer and reset the writing offset.
682: */
683: if (packNum == 1) {
684: mmsBuffer = new byte[totalLen];
685: mmsOffset = 0;
686: }
687:
688: /*
689: * Append "count" bytes from the packet to the end of the assembly
690: * buffer. The data immediately follow the 10-byte header.
691: */
692: for (int i = 0; i < count; i++) {
693: mmsBuffer[mmsOffset++] = data[i];
694: }
695:
696: /*
697: * If the last packet has been received, reset the expected packet
698: * number and the writing offset. The assembly buffer cannot be reset,
699: * as it contains the latest assembled data.
700: */
701: if (packNum == totalPackets) {
702: packetNumber = 1;
703: mmsOffset = 0;
704:
705: // Indicate that all data have been assembled.
706: return true;
707: }
708:
709: // Bump the expected packet number
710: packetNumber++;
711:
712: // Indicate that more packets are expected.
713: return false;
714: }
715:
716: }
|