0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.tck.wma.mms;
0028:
0029: // Interfaces
0030: import com.sun.tck.wma.MessageConnection;
0031:
0032: // Classes
0033: import com.sun.tck.wma.MessagePart;
0034: import com.sun.tck.wma.MultipartMessage;
0035: import com.sun.tck.wma.sms.MessageObject;
0036: import java.io.ByteArrayInputStream;
0037: import java.io.ByteArrayOutputStream;
0038: import java.io.DataInputStream;
0039: import java.io.DataOutputStream;
0040: import java.util.Date;
0041: import java.util.Vector;
0042:
0043: // Exceptions
0044: import com.sun.tck.wma.SizeExceededException;
0045: import java.io.IOException;
0046:
0047: /**
0048: * Implements an MMS message for the MMS message connection.
0049: */
0050: public class MultipartObject extends MessageObject implements
0051: MultipartMessage {
0052:
0053: /** The array of "to" addresses. */
0054: Vector to;
0055: /** The array of "cc" addresses. */
0056: Vector cc;
0057: /** The array of "bcc" addresses. */
0058: Vector bcc;
0059: /** The array of message parts. */
0060: Vector parts;
0061: /** The Content-ID of the part that starts the message. */
0062: String startContentID;
0063: /** The Subject field of the message. */
0064: String subject;
0065: /** The array of message headers. */
0066: String[] headerValues;
0067: /** The Application Identifier of the agent to process the message. */
0068: String applicationID;
0069: /**
0070: * The Application Identifier of the return agent to
0071: * process the message.
0072: */
0073: String replyToApplicationID;
0074:
0075: /**
0076: * Construct a multipart message and initialize the
0077: * target address.
0078: * @param toAddress the address of the recipient. May be null.
0079: */
0080: public MultipartObject(String toAddress) {
0081: super (MessageConnection.MULTIPART_MESSAGE, null);
0082: to = new Vector();
0083: cc = new Vector();
0084: bcc = new Vector();
0085: parts = new Vector();
0086: startContentID = null;
0087: subject = null;
0088: applicationID = null;
0089: replyToApplicationID = null;
0090: setupHeaderFields();
0091: if (toAddress != null) {
0092: addAddress("to", toAddress);
0093: }
0094: }
0095:
0096: /**
0097: * Gets the "reply-to" application identifier.
0098: * @return the return address application identifier, or null if none is set
0099: * @see #setReplyToApplicationID
0100: */
0101: String getReplyToApplicationID() {
0102: return replyToApplicationID;
0103: }
0104:
0105: /**
0106: * Sets the "reply-to" application identifier.
0107: * @param appID the return address application identifier. May be null.
0108: * @see #getReplyToApplicationID
0109: */
0110: public void setReplyToApplicationID(String appID) {
0111: replyToApplicationID = appID;
0112: }
0113:
0114: /**
0115: * Returns the destination application identifier.
0116: * @return the destination application identifier, or null if none is set
0117: */
0118: public String getApplicationID() {
0119: return applicationID;
0120: }
0121:
0122: /**
0123: * Sets the "from" address.
0124: * @param fromAddress the return address, which may be null
0125: */
0126: public void setFromAddress(String fromAddress) {
0127: super .setAddress(fromAddress);
0128: }
0129:
0130: /**
0131: * Prepares a received message to be sent right back to the sender.
0132: * Removes this device's address from the "to" and "cc"
0133: * address lists and sets the sender's address as the first "to" address.
0134: * @param senderAddress the sender's address. Should not include the
0135: * <code>"mms://"</code> prefix, and may contain the
0136: * <code>":appID"</code> suffix. May be null.
0137: * @param myAddress this device's address. Must not be null.
0138: */
0139: public void fixupReceivedMessageAddresses(String senderAddress,
0140: String myAddress) {
0141: String regularAddress = myAddress;
0142: String plusAddress = myAddress;
0143: if (regularAddress.charAt(0) == '+') {
0144: regularAddress = regularAddress.substring(1);
0145: } else if (plusAddress.charAt(0) != '+') {
0146: plusAddress = "+" + plusAddress;
0147: }
0148: // remove ourselves from "to" and "cc" list
0149: Vector addresses = to;
0150: for (int i = 0; i < 2; ++i) {
0151: int numAdds = addresses.size();
0152: for (int index = 0; index < numAdds; ++index) {
0153: String this Address = (String) addresses
0154: .elementAt(index);
0155: MMSAddress parsedAddress = MMSAddress
0156: .getParsedMMSAddress(this Address);
0157: if (parsedAddress != null
0158: && (regularAddress
0159: .equals(parsedAddress.address) || plusAddress
0160: .equals(parsedAddress.address))) {
0161: --numAdds;
0162: addresses.removeElementAt(index);
0163: --index;
0164: }
0165: }
0166: addresses = cc;
0167: }
0168: // set the first "to" address to be the sender's address
0169: if (senderAddress != null) {
0170: String formalAddress = senderAddress;
0171: to.insertElementAt(formalAddress, 0);
0172: MMSAddress parsedAddress = MMSAddress
0173: .getParsedMMSAddress(formalAddress);
0174: applicationID = parsedAddress.appId;
0175: } else {
0176: applicationID = null;
0177: }
0178: }
0179:
0180: /**
0181: * Gets the requested address list.
0182: *
0183: * @param type the address list to be returned, either "to", "cc" or "bcc"
0184: * @exception IllegalArgumentException if some other
0185: * address list type is requested
0186: * @return a list of addresses
0187: */
0188: Vector getAddressList(String type) {
0189: String lower = type.toLowerCase();
0190: if (lower.equals("to")) {
0191: return to;
0192: } else if (lower.equals("cc")) {
0193: return cc;
0194: } else if (lower.equals("bcc")) {
0195: return bcc;
0196: }
0197: throw new IllegalArgumentException(
0198: "Address type is not 'to', 'cc', or 'bcc'");
0199: }
0200:
0201: /**
0202: * Checks if the string is a valid MMS address according to the grammar
0203: * in Appendix D of the spec.
0204: * @param addr the address to check
0205: * @return MMSAddress representing the valid address, otherwise null.
0206: */
0207: MMSAddress checkValidAddress(String addr)
0208: throws IllegalArgumentException {
0209: MMSAddress parsedAddress = MMSAddress.getParsedMMSAddress(addr);
0210: // check to make sure there's a device address
0211: if (parsedAddress == null
0212: || parsedAddress.type == MMSAddress.INVALID_ADDRESS
0213: || parsedAddress.type == MMSAddress.APP_ID) {
0214: throw new IllegalArgumentException(
0215: "Invalid destination address: " + addr);
0216: }
0217: return parsedAddress;
0218: }
0219:
0220: /**
0221: * Checks an application ID to see if it can be legally added to this
0222: * message. The spec requires that only a single applicationID can be
0223: * specified for any <code>MultipartMessage</code>.
0224: * @param newAppID the candidate applicationID to check. May be
0225: * <code>null</code>
0226: * @throws IllegalArgumentException if newAppID conflicts with an
0227: * applicationID already specified for this message.
0228: */
0229: void checkApplicationID(String newAppID)
0230: throws IllegalArgumentException {
0231: if (applicationID != null) {
0232: if (!applicationID.equals(newAppID)) {
0233: throw new IllegalArgumentException(
0234: "Only one Application-ID can be specified per message");
0235: }
0236: } else {
0237: applicationID = newAppID;
0238: }
0239: }
0240:
0241: /** Array of allowed MMS header fields. */
0242: static final String[] ALLOWED_HEADER_FIELDS = {
0243: "X-Mms-Delivery-Time", "X-Mms-Priority" };
0244:
0245: /** Array of default header values. */
0246: static final String[] DEFAULT_HEADER_VALUES = { null, "Normal" };
0247:
0248: /** Array of known header fields. */
0249: static final String[] KNOWN_HEADER_FIELDS = {
0250: // mandatory fields
0251: "X-Mms-Message-Type", "X-Mms-Transaction-ID",
0252: "X-Mms-MMS-Version", "X-Mms-Content-Type",
0253: // optional fields supported by this implementation
0254: "X-Mms-Subject", "X-Mms-From", "X-Mms-To", "X-Mms-CC",
0255: "X-Mms-BCC" };
0256:
0257: /**
0258: * Checks if header field is known.
0259: *
0260: * @param headerField the header field key to check
0261: * @return <code>true</code> if the header is known
0262: */
0263: static boolean isKnownHeaderField(String headerField) {
0264: String lowerFieldName = headerField.toLowerCase();
0265: for (int i = 0; i < KNOWN_HEADER_FIELDS.length; ++i) {
0266: if (lowerFieldName.equals(KNOWN_HEADER_FIELDS[i]
0267: .toLowerCase())) {
0268: return true;
0269: }
0270: }
0271: return false;
0272: }
0273:
0274: /**
0275: * Sets default values for all allowed header fields.
0276: */
0277: void setupHeaderFields() {
0278: headerValues = new String[ALLOWED_HEADER_FIELDS.length];
0279: for (int i = 0; i < DEFAULT_HEADER_VALUES.length; ++i) {
0280: headerValues[i] = DEFAULT_HEADER_VALUES[i];
0281: }
0282: }
0283:
0284: /**
0285: * Gets the location of the requested header from the
0286: * list of allowed header fields.
0287: *
0288: * @param headerField the header field key to be checked
0289: * @return the index of the requested header field, or
0290: * -1 if the header is not supported
0291: */
0292: static int getHeaderFieldIndex(String headerField) {
0293: String lowerFieldName = headerField.toLowerCase();
0294: for (int i = 0; i < ALLOWED_HEADER_FIELDS.length; ++i) {
0295: if (lowerFieldName.equals(ALLOWED_HEADER_FIELDS[i]
0296: .toLowerCase())) {
0297: return i;
0298: }
0299: }
0300: return -1;
0301: }
0302:
0303: /**
0304: * Checks if allowed to access the requested header field.
0305: *
0306: * @param field the header field key to check
0307: * @return <code>true</code> if the header exists
0308: */
0309: boolean isAllowedToAccessHeaderField(String field) {
0310: return (getHeaderFieldIndex(field) != -1);
0311: }
0312:
0313: /**
0314: * Checks the header field value.
0315: * @param headerIndex the index of the header field to check
0316: * @param value the value to be checked
0317: * @exception Error if an invalid header index is requested
0318: * @exception IllegalArgumentException if the value is not
0319: * a valid delivery time or priority
0320: */
0321: static void checkHeaderValue(int headerIndex, String value) {
0322: switch (headerIndex) {
0323: case 0: // X-Mms-Delivery-Time
0324: try {
0325: Long.parseLong(value);
0326: return;
0327: } catch (NumberFormatException nfe) {
0328: // do nothing... we'll report the error in a second
0329: }
0330: break;
0331: case 1: // X-Mms-Priority
0332: {
0333: String lower = value.toLowerCase();
0334: if (lower.equals("normal") || lower.equals("high")
0335: || lower.equals("low")) {
0336: return;
0337: }
0338: // we'll report the error in a second
0339: break;
0340: }
0341: default:
0342: throw new Error("Unknown headerIndex: " + headerIndex);
0343: }
0344: // report the error
0345: throw new IllegalArgumentException("Illegal value for header "
0346: + ALLOWED_HEADER_FIELDS[headerIndex] + ": " + value);
0347: }
0348:
0349: /**
0350: * Adds an address to the multipart message.
0351: * @param type the address type ("to", "cc" or "bcc") as a
0352: * <code>String</code>. Each message can have none or multiple "to",
0353: * "cc" and "bcc" addresses. Each address is added separately. The
0354: * type is not case-sensitive.
0355: * @param address the address as a <code>String</code>
0356: * @return <code>true</code> if it was possible to add the address, else
0357: * <code>false</code>
0358: * @exception java.lang.IllegalArgumentException if type is none of
0359: * "to", "cc", or "bcc" or if <code>address</code> is not valid.
0360: * @see #setAddress(String)
0361: */
0362: public boolean addAddress(java.lang.String type,
0363: java.lang.String address) throws IllegalArgumentException {
0364:
0365: MMSAddress parsedAddress = checkValidAddress(address);
0366: String appID = parsedAddress.appId;
0367: if (appID != null) {
0368: checkApplicationID(appID);
0369: }
0370:
0371: Vector which = getAddressList(type);
0372: if (!which.contains(address)) {
0373: which.addElement(address);
0374: return true;
0375: }
0376: return false;
0377: }
0378:
0379: /**
0380: * Maximum size of MMS message.
0381: * Default value is 30730 (30K)
0382: */
0383: static final int MAX_TOTAL_SIZE = 30730;
0384:
0385: /**
0386: * Attaches a <code>MessagePart</code> to the multipart message.
0387: * @param part <code>MessagePart</code> to add
0388: * @exception java.lang.IllegalArgumentException if the Content-ID of the
0389: * <code>MessagePart</code> conflicts with a Content-ID of a
0390: * <code>MessagePart</code> already contained in this
0391: * <code>MultiPartMessage</code>. The Content-IDs must be unique
0392: * within a MultipartMessage.
0393: * @exception NullPointerException if the parameter is <code>null</code>
0394: * @exception SizeExceededException if it's not possible to attach the
0395: * <code>MessagePart</code>.
0396: */
0397: public void addMessagePart(MessagePart part)
0398: throws SizeExceededException {
0399: String this ContentID = part.getContentID();
0400: boolean duplicateContentID = false;
0401: int totalSizeSoFar = 0;
0402: int numPartsSoFar = parts.size();
0403: for (int i = 0; i < numPartsSoFar; ++i) {
0404: MessagePart onePart = (MessagePart) parts.elementAt(i);
0405: if (this ContentID.equals(onePart.getContentID())) {
0406: throw new IllegalArgumentException(
0407: "Cannot add duplicate content-id: "
0408: + this ContentID);
0409: }
0410: totalSizeSoFar += onePart.getLength();
0411: }
0412: if (totalSizeSoFar + part.getLength() > MAX_TOTAL_SIZE) {
0413: throw new SizeExceededException(
0414: "Adding this MessagePart would exceed max size of "
0415: + MAX_TOTAL_SIZE + " bytes");
0416: }
0417: parts.addElement(part);
0418: }
0419:
0420: /**
0421: * Returns the "from" address associated with this message, e.g. address of
0422: * the sender. If the message is a newly created message, e.g. not a
0423: * received one, then the first "to" address is returned.
0424: * Returns <code>null</code> if the "from" or "to" address for the
0425: * message, dependent on the case, are not set.
0426: * Note: This design allows sending responses to a received message easily
0427: * by reusing the same <code>Message</code> object and just replacing the
0428: * payload. The address field can normally be kept untouched (unless the
0429: * used messaging protocol requires some special handling of the address).
0430: * @return the "from" or "to" address of this message, or <code>null</code>
0431: * if the address that is expected as a result of this method is not
0432: * set
0433: * @see #setAddress(String)
0434: */
0435: public java.lang.String getAddress() {
0436: String returnMe = null;
0437: Date tStamp = getTimestamp();
0438: if (tStamp == null || tStamp.getTime() == 0L) {
0439: // not a received message - use the first "to" address
0440: if (to.size() > 0) {
0441: returnMe = (String) to.elementAt(0);
0442: }
0443: } else {
0444: // received - use the "from" address
0445: returnMe = super .getAddress();
0446: }
0447: return returnMe;
0448: }
0449:
0450: /**
0451: * Gets the addresses of the multipart message of the specified type (e.g.
0452: * "to", "cc", "bcc" or "from") as <code>String</code>. The method is not
0453: * case sensitive.
0454: * @param type the type of addresses to return
0455: * @return the addresses as a <code>String</code> array or <code>null</code>
0456: * if this value is not present.
0457: */
0458: public java.lang.String[] getAddresses(java.lang.String type) {
0459: if (type.toLowerCase().equals("from")) {
0460: String address = super .getAddress();
0461: if (address == null) {
0462: return null;
0463: }
0464: return new String[] { address };
0465: }
0466: Vector which = getAddressList(type);
0467: int num = which.size();
0468: if (num == 0) {
0469: return null;
0470: }
0471: String[] addresses = new String[num];
0472: which.copyInto(addresses);
0473: return addresses;
0474: }
0475:
0476: /**
0477: * Gets the content of the specific header field of the multipart message.
0478: * @param headerField the name of the header field as a <code>String</code>
0479: * @return the content of the specified header field as a
0480: * <code>String</code> or <code>null</code> of the specified header
0481: * field is not present.
0482: * @exception SecurityException if the access to specified header field is
0483: * restricted
0484: * @exception IllegalArgumentException if <code>headerField</code>
0485: * is unknown
0486: * @see Appendix D for known headerFields
0487: * @see #setHeader
0488: */
0489: public java.lang.String getHeader(java.lang.String headerField) {
0490: if (headerField == null) {
0491: throw new IllegalArgumentException(
0492: "headerField must not be null");
0493: }
0494: if (isAllowedToAccessHeaderField(headerField)) {
0495: int index = getHeaderFieldIndex(headerField);
0496: if (index != -1) {
0497: return headerValues[index];
0498: }
0499: throw new Error(
0500: "Allowed to access field but it has no index");
0501: }
0502: if (isKnownHeaderField(headerField)) {
0503: throw new SecurityException(
0504: "Cannot access restricted header field: "
0505: + headerField);
0506: } else {
0507: throw new IllegalArgumentException("Unknown header field: "
0508: + headerField);
0509: }
0510: }
0511:
0512: /**
0513: * This method returns a <code>MessagePart</code> from the message that
0514: * matches the content-id passed as a parameter.
0515: * @param contentID the content-id for the <code>MessagePart</code> to be
0516: * returned
0517: * @return <code>MessagePart</code> that matches the provided content-id or
0518: * <code>null</code> if there is no <code>MessagePart</code> in this
0519: * message with the provided content-id
0520: * @exception NullPointerException if the parameter is <code>null</code>
0521: */
0522: public MessagePart getMessagePart(java.lang.String contentID) {
0523: if (contentID == null) {
0524: throw new NullPointerException("contentID must not be null");
0525: }
0526: int numParts = parts.size();
0527: for (int i = 0; i < numParts; ++i) {
0528: MessagePart onePart = (MessagePart) parts.elementAt(i);
0529: if (contentID.equals(onePart.getContentID())) {
0530: return onePart;
0531: }
0532: }
0533: return null;
0534: }
0535:
0536: /**
0537: * Returns an array of all <code>MessagePart</code>s of this message.
0538: * @return array of <code>MessagePart</code>s, or <code>null</code> if no
0539: * <code>MessagePart</code>s are available
0540: */
0541: public MessagePart[] getMessageParts() {
0542: int num = parts.size();
0543: if (num == 0) {
0544: return null;
0545: }
0546: MessagePart[] msgParts = new MessagePart[num];
0547: parts.copyInto(msgParts);
0548: return msgParts;
0549: }
0550:
0551: /**
0552: * Returns the <code>contentId</code> of the start <code>MessagePart</code>.
0553: * The start <code>MessagePart</code> is set in
0554: * <code>setStartContentId(String)</code>
0555: * @return the content-id of the start <code>MessagePart</code> or
0556: * <code>null</code> if the start <code>MessagePart</code> is not set.
0557: * @see #setStartContentId(String)
0558: */
0559: public java.lang.String getStartContentId() {
0560: return startContentID;
0561: }
0562:
0563: /**
0564: * Gets the subject of the multipart message.
0565: * @return the message subject as a <code>String</code> or <code>null</code>
0566: * if this value is not present.
0567: * @see #setSubject
0568: */
0569: public java.lang.String getSubject() {
0570: return subject;
0571: }
0572:
0573: /**
0574: * Cleans application Id value.
0575: */
0576: private void cleanupAppID() throws IllegalStateException {
0577: Vector addresses = to;
0578: boolean checkedTo = false;
0579: boolean checkedCC = false;
0580: int currIndex = 0;
0581: boolean matchedAppID = false;
0582: while (true) {
0583: if (currIndex >= addresses.size()) {
0584: if (!checkedTo) {
0585: checkedTo = true;
0586: addresses = cc;
0587: currIndex = 0;
0588: continue;
0589: } else if (!checkedCC) {
0590: checkedCC = true;
0591: addresses = bcc;
0592: currIndex = 0;
0593: continue;
0594: } else {
0595: break;
0596: }
0597:
0598: }
0599: String addr = (String) addresses.elementAt(currIndex++);
0600: MMSAddress parsedAddress = MMSAddress
0601: .getParsedMMSAddress(addr);
0602: if (parsedAddress == null
0603: || parsedAddress.type == MMSAddress.INVALID_ADDRESS
0604: || parsedAddress.type == MMSAddress.APP_ID) {
0605: throw new IllegalStateException("Invalid MMS address: "
0606: + addr);
0607: }
0608: String this AppID = parsedAddress.appId;
0609: if (this AppID != null && this AppID.equals(applicationID)) {
0610: matchedAppID = true;
0611: }
0612: }
0613: if (!matchedAppID) {
0614: applicationID = null;
0615: }
0616: }
0617:
0618: /**
0619: * Removes an address from the multipart message.
0620: * @param type the address type ("to", "cc", or "bcc") as a
0621: * <code>String</code>
0622: * @param address the address as a <code>String</code>
0623: * @return <code>true</code> if it was possible to delete the address, else
0624: * <code>false</code>
0625: * @throws NullPointerException is type is <code>null</code>
0626: * @throws java.lang.IllegalArgumentException if type is none of "to", "cc",
0627: * or "bcc"
0628: */
0629: public boolean removeAddress(java.lang.String type,
0630: java.lang.String address) {
0631: Vector which = getAddressList(type);
0632: boolean result = which.removeElement(address);
0633: cleanupAppID();
0634: return result;
0635: }
0636:
0637: /**
0638: * Removes all addresses of types "to", "cc", "bcc" from the
0639: * multipart message.
0640: * @see #setAddress(String)
0641: * @see #addAddress(String, String)
0642: */
0643: public void removeAddresses() {
0644: to.removeAllElements();
0645: cc.removeAllElements();
0646: bcc.removeAllElements();
0647: applicationID = null;
0648: }
0649:
0650: /**
0651: * Removes all addresses of the specified type from the multipart message.
0652: * @param type the address type ("to", "cc", or "bcc") as a
0653: * <code>String</code>
0654: * @throws NullPointerException if type is <code>null</code>
0655: * @throws java.lang.IllegalArgumentException if type is none of "to", "cc",
0656: * or "bcc"
0657: */
0658: public void removeAddresses(java.lang.String type) {
0659: Vector which = getAddressList(type);
0660: which.removeAllElements();
0661: cleanupAppID();
0662: }
0663:
0664: /**
0665: * Removes a <code>MessagePart</code> from the multipart message.
0666: * @param part <code>MessagePart</code> to delete
0667: * @return <code>true</code> if it was possible to remove the
0668: * <code>MessagePart</code>, else <code>false</code>
0669: * @exception NullPointerException id the parameter is <code>null</code>
0670: */
0671: public boolean removeMessagePart(MessagePart part) {
0672: if (part == null) {
0673: throw new NullPointerException("part must not be null");
0674: }
0675: if (part.getContentID().equals(startContentID)) {
0676: startContentID = null;
0677: }
0678: return parts.removeElement(part);
0679: }
0680:
0681: /**
0682: * Removes a <code>MessagePart</code> with the specific
0683: * <code>contentID</code> from the multipart message.
0684: * @param contentID identifies which <code>MessagePart</code> must be
0685: * deleted.
0686: * @return <code>true</code> if it was possible to remove the
0687: * <code>MessagePart</code>, else <code>false</code>
0688: * @exception NullPointerException if the parameter is <code>null</code>
0689: */
0690: public boolean removeMessagePartId(java.lang.String contentID) {
0691: if (contentID == null) {
0692: throw new NullPointerException("contentID must not be null");
0693: }
0694: int numParts = parts.size();
0695: for (int i = 0; i < numParts; ++i) {
0696: MessagePart onePart = (MessagePart) parts.elementAt(i);
0697: if (contentID.equals(onePart.getContentID())) {
0698: if (contentID.equals(startContentID)) {
0699: startContentID = null;
0700: }
0701: parts.removeElementAt(i);
0702: return true;
0703: }
0704: }
0705: return false;
0706: }
0707:
0708: /**
0709: * Removes <code>MessagePart</code>s with the specific content location
0710: * from the multipart message. All <code>MessagePart</code>s with the
0711: * specified <code>contentLocation</code> are removed.
0712: * @param contentLocation content location (file name) of the
0713: * <code>MessagePart</code>
0714: * @return <code>true</code> if it was possible to remove the
0715: * <code>MessagePart</code>, else <code>false</code>
0716: * @exception NullPointerException if the parameter is <code>null</code>
0717: */
0718: public boolean removeMessagePartLocation(
0719: java.lang.String contentLocation) {
0720: if (contentLocation == null) {
0721: throw new NullPointerException(
0722: "contentLocation must not be null");
0723: }
0724: int numParts = parts.size();
0725: boolean found = false;
0726: for (int i = 0; i < numParts; ++i) {
0727: MessagePart onePart = (MessagePart) parts.elementAt(i);
0728: if (contentLocation.equals(onePart.getContentLocation())) {
0729: if (onePart.getContentID().equals(startContentID)) {
0730: startContentID = null;
0731: }
0732: parts.removeElementAt(i);
0733: --numParts;
0734: --i;
0735: found = true;
0736: }
0737: }
0738: return found;
0739: }
0740:
0741: /**
0742: * Sets the "to" address associated with this message. It works the same way
0743: * as <code>addAddress("to", addr)</code>. The address may be set to
0744: * <code>null</code>.
0745: * @param address address for the message
0746: * @see #getAddress()
0747: * @see #addAddress(String, String)
0748: */
0749: public void setAddress(java.lang.String address) {
0750: if (address != null) {
0751: addAddress("to", address);
0752: }
0753: // otherwise it's a no-op.
0754: }
0755:
0756: /**
0757: * Sets the specified header of the multipart message. The header value can
0758: * be <code>null</code>.
0759: * @param headerField the name of the header field as a <code>String</code>
0760: * @param headerValue the value of the header as a <code>String</code>
0761: * @exception java.lang.IllegalArgumentException if
0762: * <code>headerField</code> is unknown, or if
0763: * <code>headerValue</code> is not correct (depends on
0764: * <code>headerField</code>!)
0765: * @exception NullPointerException if <code>headerField</code> is
0766: * <code>null</code>
0767: * @exception SecurityException if the access to specified header field is
0768: * restricted
0769: * @see #getHeader(String)
0770: * @see Appendix D
0771: */
0772: public void setHeader(java.lang.String headerField,
0773: java.lang.String headerValue) {
0774: if (isAllowedToAccessHeaderField(headerField)) {
0775: int index = getHeaderFieldIndex(headerField);
0776: if (index != -1) {
0777: if (headerValue != null) {
0778: checkHeaderValue(index, headerValue);
0779: }
0780: headerValues[index] = headerValue;
0781: return;
0782: }
0783: throw new Error(
0784: "Allowed to access field but it has no index");
0785: }
0786: if (isKnownHeaderField(headerField)) {
0787: throw new SecurityException(
0788: "Cannot access restricted header field: "
0789: + headerField);
0790: } else {
0791: throw new IllegalArgumentException("Unknown header field: "
0792: + headerField);
0793: }
0794: }
0795:
0796: /**
0797: * Sets the <code>Content-ID</code> of the start <code>MessagePart</code> of
0798: * a multipart related message. The <code>Content-ID</code> may be set to
0799: * <code>null</code>. The <code>StartContentId</code> is set for the
0800: * MessagePart that is used to reference the other MessageParts of the
0801: * MultipartMessage for presentation or processing purposes.
0802: * @param contentId as a <code>String</code>
0803: * @exception java.lang.IllegalArgumentException if
0804: * <code>contentId</code> is none of the added
0805: * <code>MessagePart</code> objects matches the
0806: * <code>contentId</code>
0807: * @see #getStartContentId()
0808: */
0809: public void setStartContentId(java.lang.String contentId) {
0810: if (contentId != null) {
0811: if (getMessagePart(contentId) == null) {
0812: throw new IllegalArgumentException(
0813: "Unknown contentId: " + contentId);
0814: }
0815: }
0816: startContentID = contentId;
0817: }
0818:
0819: /**
0820: * Sets the Subject of the multipart message. This value can be
0821: * <code>null</code>.
0822: * @param subject the message subject as a <code>String</code>
0823: * @see #getSubject()
0824: */
0825: public void setSubject(java.lang.String subject) {
0826: if (subject != null && subject.length() > 40) { // MMS Conformance limit
0827: throw new IllegalArgumentException(
0828: "Subject exceeds 40 chars");
0829: }
0830: this .subject = subject;
0831: }
0832:
0833: /**
0834: * Returns only the device part of the MMS Address.
0835: * @return the device portion of the MMS Address
0836: * @param address the MMS address
0837: * @throws IllegalArgumentException if the MMS Address has no
0838: * device portion.
0839: */
0840: static String getDevicePortionOfAddress(String address)
0841: throws IllegalArgumentException {
0842: MMSAddress parsedAddress = MMSAddress
0843: .getParsedMMSAddress(address);
0844: if (parsedAddress == null || parsedAddress.address == null) {
0845: throw new IllegalArgumentException("MMS Address "
0846: + "has no device portion");
0847: }
0848: return parsedAddress.address;
0849: }
0850:
0851: /**
0852: * Writes a vector to an output stream. If the contents are MMS addresses,
0853: * as indicated by the <code>isAddress</code> parameter, then
0854: * only the device
0855: * part of the address is placed into the vector, not the
0856: * application-id, if any.
0857: * @param dos the data output stream for writing
0858: * @param v the array to be written
0859: * @param isAddress is the contents of the vector an MMS address.
0860: * @exception IOException if any I/O errors occur
0861: */
0862: static void writeVector(DataOutputStream dos, Vector v,
0863: boolean isAddress) throws IOException {
0864: StringBuffer buff = new StringBuffer();
0865: int len = v.size();
0866: String appendMe = null;
0867: if (len > 0) {
0868: appendMe = (String) v.elementAt(0);
0869: if (isAddress) {
0870: appendMe = getDevicePortionOfAddress(appendMe);
0871: }
0872: buff.append(appendMe);
0873: }
0874: for (int i = 1; i < len; ++i) {
0875: buff.append("; ");
0876: appendMe = (String) v.elementAt(i);
0877: if (isAddress) {
0878: appendMe = getDevicePortionOfAddress(appendMe);
0879: }
0880: buff.append(appendMe);
0881: }
0882: dos.writeUTF(buff.toString());
0883: }
0884:
0885: /**
0886: * Reads a vector from an input stream. If the content is an MMS Address,
0887: * as indicated by the <code>isAddress</code> parameter, then the prefix
0888: * <code>"mms://"</code> is added to each address.
0889: * @param dis the data input stream for reading
0890: * @param v the array to be returned
0891: * @param isAddress the contents are MMS Addresses
0892: * @exception IOException if any I/O errors occur
0893: */
0894: static void readVector(DataInputStream dis, Vector v,
0895: boolean isAddress) throws IOException {
0896: String inputStr = dis.readUTF();
0897: int prevDelim = -2;
0898: String prefix = "";
0899: if (isAddress) {
0900: prefix = "mms://";
0901: }
0902: while (prevDelim != -1) {
0903: int nextDelim = inputStr.indexOf("; ", prevDelim + 2);
0904: String addStr = null;
0905: if (nextDelim == -1) {
0906: addStr = prefix + inputStr.substring(prevDelim + 2);
0907: } else {
0908: addStr = prefix
0909: + inputStr.substring(prevDelim + 2, nextDelim);
0910: }
0911: v.addElement(addStr);
0912: prevDelim = nextDelim;
0913: }
0914: }
0915:
0916: /**
0917: * Writes a message part to the output stream
0918: * @param dos the data output stream for writing
0919: * @param p the message part to be written
0920: * @exception IOException if any I/O errors occur
0921: */
0922: static void writeMessagePart(DataOutputStream dos, MessagePart p)
0923: throws IOException {
0924: dos.writeUTF("Content-Type");
0925: StringBuffer contentType = new StringBuffer(p.getMIMEType());
0926: String loc = p.getContentLocation();
0927: if (loc != null) {
0928: contentType.append("; name=\"");
0929: contentType.append(loc);
0930: contentType.append("\"");
0931: }
0932: dos.writeUTF(contentType.toString());
0933: String id = p.getContentID();
0934: if (id != null) {
0935: dos.writeUTF("Content-ID");
0936: dos.writeUTF(id);
0937: }
0938: String enc = p.getEncoding();
0939: if (enc != null) {
0940: dos.writeUTF("Encoding");
0941: dos.writeUTF(enc);
0942: }
0943: // the payload
0944: dos.writeUTF("Content-Length");
0945: dos.writeInt(p.getLength());
0946: dos.writeUTF("Content");
0947: dos.write(p.getContent());
0948: }
0949:
0950: /**
0951: * Create a new message part from the input stream
0952: * @param dis the data input stream for reading
0953: * @exception IOException if any I/O errors occur
0954: * @return the message object instance
0955: */
0956: static MessagePart createMessagePart(DataInputStream dis)
0957: throws IOException {
0958: String nextField = dis.readUTF(); // eats "Content-Type" header
0959: String contentType = dis.readUTF();
0960: nextField = dis.readUTF();
0961: String contentID = null;
0962: if (nextField.equals("Content-ID")) {
0963: contentID = dis.readUTF();
0964: nextField = dis.readUTF();
0965: }
0966: String encoding = null;
0967: if (nextField.equals("Encoding")) {
0968: encoding = dis.readUTF();
0969: nextField = dis.readUTF();
0970: }
0971: // "Content-Length" was just eaten
0972: int length = dis.readInt();
0973: byte[] contents = new byte[length];
0974: nextField = dis.readUTF(); // eats the "Content" header
0975: dis.readFully(contents);
0976: // now separate the content location and mime type
0977: String mimeType = contentType;
0978: String contentLocation = null;
0979: int sepPos = contentType.indexOf(';');
0980: if (sepPos != -1
0981: && contentType.substring(sepPos)
0982: .startsWith("; name=\"")) {
0983: contentLocation = contentType.substring(sepPos + 8, // ; name="
0984: contentType.length() - 1);
0985: mimeType = contentType.substring(0, sepPos);
0986: }
0987: return new MessagePart(contents, mimeType, contentID,
0988: contentLocation, encoding);
0989: }
0990:
0991: /** The content type for the MMS message. */
0992: static final String STREAM_SIGNATURE = "application/vnd.wap.mms-message";
0993:
0994: /**
0995: * Gets the message object as a byte array.
0996: *
0997: * @exception IOException if any I/O errors occur
0998: * @return the serialized byte array of the message object
0999: */
1000: public byte[] getAsByteArray() throws IOException {
1001:
1002: ByteArrayOutputStream baos = new ByteArrayOutputStream();
1003: DataOutputStream dos = new DataOutputStream(baos);
1004:
1005: dos.writeUTF(STREAM_SIGNATURE);
1006:
1007: dos.writeUTF("X-Mms-Message-Type");
1008: dos.writeUTF("m-send-req");
1009: dos.writeUTF("X-Mms-Transaction-ID");
1010: dos.writeUTF(String.valueOf(System.currentTimeMillis()));
1011: dos.writeUTF("X-Mms-Version");
1012: dos.writeUTF("1.0");
1013: for (int i = 0; i < ALLOWED_HEADER_FIELDS.length; ++i) {
1014: String headerValue = headerValues[i];
1015: if (headerValue != null) {
1016: dos.writeUTF(ALLOWED_HEADER_FIELDS[i]);
1017: dos.writeUTF(headerValue);
1018: }
1019: }
1020: String fromAddress = super .getAddress();
1021: if (fromAddress != null) {
1022: dos.writeUTF("From");
1023: dos.writeUTF(getDevicePortionOfAddress(fromAddress));
1024: }
1025: if (to.size() != 0) {
1026: dos.writeUTF("To");
1027: writeVector(dos, to, true);
1028: }
1029: if (cc.size() != 0) {
1030: dos.writeUTF("Cc");
1031: writeVector(dos, cc, true);
1032: }
1033: if (bcc.size() != 0) {
1034: dos.writeUTF("Bcc");
1035: writeVector(dos, bcc, true);
1036: }
1037: long date = 0L;
1038: Date tStamp = getTimestamp();
1039: if (tStamp != null && (date = tStamp.getTime()) != 0L) {
1040: dos.writeUTF("Date");
1041: dos.writeUTF(String.valueOf(date));
1042: }
1043: if (subject != null) {
1044: dos.writeUTF("Subject");
1045: dos.writeUTF(subject);
1046: }
1047: dos.writeUTF("Content-Type");
1048: Vector contentTypeElements = new Vector();
1049: if (startContentID != null) {
1050: contentTypeElements
1051: .addElement("application/vnd.wap.multipart.related");
1052: } else {
1053: contentTypeElements
1054: .addElement("application/vnd.wap.multipart.mixed");
1055: }
1056: if (startContentID != null) {
1057: contentTypeElements.addElement("start = <" + startContentID
1058: + ">");
1059: contentTypeElements.addElement("type = "
1060: + getMessagePart(startContentID).getMIMEType());
1061: }
1062: if (applicationID != null) {
1063: contentTypeElements.addElement("Application-ID = "
1064: + applicationID);
1065: }
1066: if (replyToApplicationID != null) {
1067: contentTypeElements.addElement("Reply-To-Application-ID = "
1068: + replyToApplicationID);
1069: }
1070: writeVector(dos, contentTypeElements, false);
1071: dos.writeUTF("nEntries");
1072: int numParts = parts.size();
1073: dos.writeUTF(String.valueOf(numParts));
1074: for (int i = 0; i < numParts; ++i) {
1075: MessagePart p = (MessagePart) parts.elementAt(i);
1076: writeMessagePart(dos, p);
1077: }
1078: dos.close();
1079: byte[] returnMe = baos.toByteArray();
1080: baos.close();
1081: return returnMe;
1082: }
1083:
1084: /**
1085: * Create a message object from a serialized byte array.
1086: *
1087: * @param data a serialized byte array of a message object
1088: * @return the multipart message object
1089: * @exception IOException if any I/O errors occur
1090: */
1091: public static MultipartObject createFromByteArray(byte[] data)
1092: throws IOException {
1093:
1094: ByteArrayInputStream bais = new ByteArrayInputStream(data);
1095: DataInputStream dis = new DataInputStream(bais);
1096:
1097: String signature = dis.readUTF();
1098: if (!signature.equals(STREAM_SIGNATURE)) {
1099: throw new IOException("invalid data format");
1100: }
1101: // eat the first 6 entries: "X-Mms-Message-Type", "m-send-req",
1102: // "X-Mms-Transaction-ID", <transactionID>, "X-Mms-Version", "1.0"
1103: for (int i = 0; i < 6; ++i) {
1104: dis.readUTF();
1105: }
1106:
1107: String[] headerValues = new String[ALLOWED_HEADER_FIELDS.length];
1108: String nextField = dis.readUTF();
1109: int headerIndex;
1110: while ((headerIndex = getHeaderFieldIndex(nextField)) != -1) {
1111: headerValues[headerIndex] = dis.readUTF();
1112: nextField = dis.readUTF();
1113: }
1114: String fromAddress = null;
1115: if (nextField.equals("From")) {
1116: fromAddress = "mms://" + dis.readUTF();
1117: nextField = dis.readUTF();
1118: }
1119: Vector to = new Vector();
1120: if (nextField.equals("To")) {
1121: readVector(dis, to, true);
1122: nextField = dis.readUTF();
1123: }
1124: Vector cc = new Vector();
1125: if (nextField.equals("Cc")) {
1126: readVector(dis, cc, true);
1127: nextField = dis.readUTF();
1128: }
1129: Vector bcc = new Vector();
1130: if (nextField.equals("Bcc")) {
1131: readVector(dis, bcc, true);
1132: nextField = dis.readUTF();
1133: }
1134: long date = 0L;
1135: if (nextField.equals("Date")) {
1136: String dateStr = dis.readUTF();
1137: try {
1138: date = Long.parseLong(dateStr);
1139: } catch (NumberFormatException nfe) {
1140: date = 0L;
1141: }
1142: nextField = dis.readUTF();
1143: }
1144: String subject = null;
1145: if (nextField.equals("Subject")) {
1146: subject = dis.readUTF();
1147: nextField = dis.readUTF();
1148: }
1149: // nextField is "Content-Type"
1150: String startContentID = null;
1151: String applicationID = null;
1152: String replyToApplicationID = null;
1153: Vector contentTypeElements = new Vector();
1154: readVector(dis, contentTypeElements, false);
1155: int numContentTypeElements = contentTypeElements.size();
1156: for (int i = 0; i < numContentTypeElements; ++i) {
1157: String element = (String) contentTypeElements.elementAt(i);
1158: if (element.startsWith("start = <")) {
1159: startContentID = element.substring(9);
1160: startContentID = startContentID.substring(0,
1161: startContentID.length() - 1);
1162: } else if (element.startsWith("Application-ID = ")) {
1163: applicationID = element.substring(17);
1164: } else if (element.startsWith("Reply-To-Application-ID = ")) {
1165: replyToApplicationID = element.substring(26);
1166: }
1167: }
1168: nextField = dis.readUTF();
1169: // nextField is "nEntries"
1170: int numParts = 0;
1171: String numPartsStr = dis.readUTF();
1172: try {
1173: numParts = Integer.parseInt(numPartsStr);
1174: } catch (NumberFormatException nfe) {
1175: numParts = 0;
1176: }
1177: Vector parts = new Vector();
1178: for (int i = 0; i < numParts; ++i) {
1179: parts.addElement(createMessagePart(dis));
1180: }
1181: dis.close();
1182: bais.close();
1183:
1184: MultipartObject mpo = new MultipartObject(fromAddress);
1185: mpo.setTimeStamp(date);
1186: mpo.headerValues = headerValues;
1187: mpo.subject = subject;
1188: mpo.startContentID = startContentID;
1189: mpo.to = to;
1190: mpo.cc = cc;
1191: /*
1192: * Uncomment this if you want the "bcc"s to be visible to the recipients
1193: mpo.bcc = bcc;
1194: */
1195: mpo.parts = parts;
1196: mpo.applicationID = applicationID;
1197: mpo.replyToApplicationID = replyToApplicationID;
1198: return mpo;
1199: }
1200:
1201: /**
1202: * Gets the message object header as a byte array. The header is composed of
1203: * a number of fields and is exclusive of the <code>MessagePart</code>
1204: * contents.
1205: *
1206: * @throws IOException if any I/O errors occur.
1207: * @return the serialized byte array of the message object
1208: */
1209: public byte[] getHeaderAsByteArray() throws IOException {
1210:
1211: ByteArrayOutputStream baos = new ByteArrayOutputStream();
1212: DataOutputStream dos = new DataOutputStream(baos);
1213:
1214: dos.writeUTF(STREAM_SIGNATURE);
1215:
1216: // Write headers that
1217: dos.writeUTF("X-Mms-Message-Type");
1218: dos.writeUTF("m-send-req");
1219: dos.writeUTF("X-Mms-Transaction-ID");
1220: dos.writeUTF(String.valueOf(System.currentTimeMillis()));
1221: dos.writeUTF("X-Mms-Version");
1222: dos.writeUTF("1.0");
1223:
1224: for (int i = 0; i < ALLOWED_HEADER_FIELDS.length; ++i) {
1225: String headerValue = headerValues[i];
1226: if (headerValue != null) {
1227: dos.writeUTF(ALLOWED_HEADER_FIELDS[i]);
1228: dos.writeUTF(headerValue);
1229: }
1230: }
1231: String fromAddress = super .getAddress();
1232: if (fromAddress != null) {
1233: dos.writeUTF("From");
1234: dos.writeUTF(getDevicePortionOfAddress(fromAddress));
1235: }
1236: if (to.size() != 0) {
1237: dos.writeUTF("To");
1238: writeVector(dos, to, true);
1239: }
1240: if (cc.size() != 0) {
1241: dos.writeUTF("Cc");
1242: writeVector(dos, cc, true);
1243: }
1244: if (bcc.size() != 0) {
1245: dos.writeUTF("Bcc");
1246: writeVector(dos, bcc, true);
1247: }
1248: long date = 0L;
1249: Date tStamp = getTimestamp();
1250: if (tStamp != null && (date = tStamp.getTime()) != 0L) {
1251: dos.writeUTF("Date");
1252: dos.writeUTF(String.valueOf(date));
1253: }
1254: if (subject != null) {
1255: dos.writeUTF("Subject");
1256: dos.writeUTF(subject);
1257: }
1258: dos.writeUTF("Content-Type");
1259: Vector contentTypeElements = new Vector();
1260: if (startContentID != null) {
1261: contentTypeElements
1262: .addElement("application/vnd.wap.multipart.related");
1263: } else {
1264: contentTypeElements
1265: .addElement("application/vnd.wap.multipart.mixed");
1266: }
1267: if (startContentID != null) {
1268: contentTypeElements.addElement("start = <" + startContentID
1269: + ">");
1270: contentTypeElements.addElement("type = "
1271: + getMessagePart(startContentID).getMIMEType());
1272: }
1273: if (applicationID != null) {
1274: contentTypeElements.addElement("Application-ID = "
1275: + applicationID);
1276: }
1277: if (replyToApplicationID != null) {
1278: contentTypeElements.addElement("Reply-To-Application-ID = "
1279: + replyToApplicationID);
1280: }
1281: writeVector(dos, contentTypeElements, false);
1282: dos.close();
1283: byte[] returnMe = baos.toByteArray();
1284: baos.close();
1285: return returnMe;
1286: }
1287:
1288: /**
1289: * Gets the message object body as a byte array. The body is composed of a
1290: * single header that states the number of entries, followed by a serialized
1291: * array of <code>MessagePart</code> objects.
1292: *
1293: * @throws IOException if any I/O errors occur.
1294: * @return the serialized byte array of the message body.
1295: */
1296: public byte[] getBodyAsByteArray() throws IOException {
1297:
1298: ByteArrayOutputStream baos = new ByteArrayOutputStream();
1299: DataOutputStream dos = new DataOutputStream(baos);
1300:
1301: dos.writeUTF("nEntries");
1302: int numParts = parts.size();
1303: dos.writeUTF(String.valueOf(numParts));
1304: for (int i = 0; i < numParts; ++i) {
1305: MessagePart p = (MessagePart) parts.elementAt(i);
1306: writeMessagePart(dos, p);
1307: }
1308:
1309: dos.close();
1310: byte[] returnMe = baos.toByteArray();
1311: baos.close();
1312: return returnMe;
1313: }
1314:
1315: }
|