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;
028:
029: import java.io.ByteArrayInputStream;
030: import java.io.ByteArrayOutputStream;
031:
032: import java.io.IOException;
033:
034: /**
035: * Instances of the <code>MessagePart</code> class can be added to a
036: * <code>MultipartMessage</code>. Each <code>MessagePart</code> consists
037: * of the content element, MIME type and content-id. The Content can be of
038: * any type. Additionally, it's possible to specify the content location and
039: * the encoding scheme.
040: * @since WMA 2.0
041: */
042: public class MessagePart {
043:
044: // IMPL_NOTE: allow maximum part size to be externally set
045: /** Maximum size for message part. */
046: static int MAX_PART_SIZE_BYTES = 30720; // 30K
047:
048: /**
049: * Constructs a message.
050: * @param contents byte array containing the contents for the
051: * <code>MessagePart</code>.
052: * @param offset start position
053: * @param length the number of bytes to be included in the
054: * <code>MessagePart</code>.
055: * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
056: * [RFC 2046]
057: * @param contentId the content-id header field value for the
058: * <code>MessagePart</code> [RFC 2045]. The content-id is unique
059: * over all <code>MessagePart</code>s of a
060: * <code>MultipartMessage</code> and must always be set for each
061: * message part.
062: * @param contentLocation the content location which specifies the
063: * file name of the file that is attached. If the content location is
064: * set to <code>null</code> no content location will be set for this
065: * <code>MessagePart</code>.
066: * @param enc the encoding scheme for the <code>MessagePart</code>.
067: * if <code>enc</code> is set to <code>null</code> no encoding will
068: * be used for this <code>MessagePart</code>.
069: * @throws java.lang.IllegalArgumentException if mimeType or contentId is
070: * <code>null</code>. This exception will be thrown if
071: * <code>contentID</code> or <code>contentLocation</code> contains
072: * other characters than specified in US-ASCII format. This exception
073: * will be thrown if either <code>length</code> is less than 0 or
074: * <code>offset + length</code> exceeds the <code>length</code> of the
075: * <code>content</code> or if <code>offset</code> is less than 0 or if
076: * the specified encoding scheme is unknown.
077: * @throws SizeExceededException if the <code>contents</code> is larger than
078: * the available memory or supported size for the message part
079: */
080: void construct(byte[] contents, int offset, int length,
081: java.lang.String mimeType, java.lang.String contentId,
082: java.lang.String contentLocation, java.lang.String enc)
083: throws SizeExceededException {
084:
085: if (length > MAX_PART_SIZE_BYTES) {
086: throw new SizeExceededException("InputStream data exceeds "
087: + "MessagePart size limit");
088: }
089:
090: if (mimeType == null) {
091: throw new IllegalArgumentException(
092: "mimeType must be specified");
093: }
094: checkContentID(contentId);
095: checkContentLocation(contentLocation);
096: if (length < 0) {
097: throw new IllegalArgumentException("length must be >= 0");
098: }
099: if (contents != null && offset + length > contents.length) {
100: throw new IllegalArgumentException(
101: "offset + length exceeds contents length");
102: }
103: if (offset < 0) {
104: throw new IllegalArgumentException("offset must be >= 0");
105: }
106: checkEncodingScheme(enc);
107:
108: if (contents != null) {
109: this .content = new byte[length];
110: System.arraycopy(contents, offset, this .content, 0, length);
111: }
112:
113: this .mimeType = mimeType;
114: this .contentID = contentId;
115: this .contentLocation = contentLocation;
116: this .encoding = enc;
117: }
118:
119: /**
120: * Constructs a <code>MessagePart</code> object from a subset of the byte
121: * array. This constructor is only useful if the data size is small
122: * (roughly less than 10K). For larger content the <code>InputStream</code>
123: * based constructor should be used.
124: * @param contents byte array containing the contents for the
125: * <code>MessagePart</code>.
126: * @param offset start position
127: * @param length the number of bytes to be included in the
128: * <code>MessagePart</code>.
129: * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
130: * [RFC 2046]
131: * @param contentId the content-id header field value for the
132: * <code>MessagePart</code> [RFC 2045]. The content-id is unique
133: * over all <code>MessagePart</code>s of a
134: * <code>MultipartMessage</code> and must always be set for each
135: * message part.
136: * @param contentLocation the content location which specifies the
137: * file name of the file that is attached. If the content location is
138: * set to <code>null</code> no content location will be set for this
139: * <code>MessagePart</code>.
140: * @param enc the encoding scheme for the <code>MessagePart</code>.
141: * if <code>enc</code> is set to <code>null</code> no encoding will
142: * be used for this <code>MessagePart</code>.
143: * @throws java.lang.IllegalArgumentException if mimeType or contentId is
144: * <code>null</code>. This exception will be thrown if
145: * <code>contentID</code> or <code>contentLocation</code> contains
146: * other characters than specified in US-ASCII format. This exception
147: * will be thrown if either <code>length</code> is less than 0 or
148: * <code>offset + length</code> exceeds the <code>length</code> of the
149: * <code>content</code> or if <code>offset</code> is less than 0 or if
150: * the specified encoding scheme is unknown.
151: * @throws SizeExceededException if the <code>contents</code> is larger than
152: * the available memory or supported size for the message part
153: */
154: public MessagePart(byte[] contents, int offset, int length,
155: java.lang.String mimeType, java.lang.String contentId,
156: java.lang.String contentLocation, java.lang.String enc)
157: throws SizeExceededException {
158: construct(contents, offset, length, mimeType, contentId,
159: contentLocation, enc);
160: }
161:
162: /**
163: * Construct a <code>MessagePart</code> object from a byte array. This
164: * constructor is only useful if the data size is small (roughly 10K).
165: * For larger content the <code>InputStream</code> based constructor
166: * should be used.
167: * @param contents byte array containing the contents for the
168: * <code>MessagePart</code>. The contents of the array will be
169: * copied into the <code>MessagePart</code>.
170: * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
171: * [RFC 2046]
172: * @param contentId the content-id header field value for the
173: * <code>MessagePart</code> [RFC 2045]. The content-id is unique
174: * over all <code>MessagePart</code>s of a
175: * <code>MultipartMessage</code> and must always be set for each
176: * message part.
177: * @param contentLocation the content location which specifies the
178: * file name of the file that is attached. If the content location is
179: * set to <code>null</code> no content location will be set for this
180: * <code>MessagePart</code>.
181: * @param enc the encoding scheme for the <code>MessagePart</code>.
182: * if <code>enc</code> is set to <code>null</code> no encoding will
183: * be used for this <code>MessagePart</code>.
184: * @throws java.lang.IllegalArgumentException if mimeType or contentId is
185: * <code>null</code>. This exception will be thrown if
186: * <code>contentID</code> or <code>contentLocation</code> contains
187: * other characters than specified in US-ASCII format or if
188: * the specified encoding scheme is unknown.
189: * @throws SizeExceededException if the <code>contents</code> is larger than
190: * the available memory or supported size for the message part
191: */
192: public MessagePart(byte[] contents, java.lang.String mimeType,
193: java.lang.String contentId,
194: java.lang.String contentLocation, java.lang.String enc)
195: throws SizeExceededException {
196: construct(contents, 0,
197: (contents == null ? 0 : contents.length), mimeType,
198: contentId, contentLocation, enc);
199: }
200:
201: /** Buffer size 2048. */
202: static final int BUFFER_SIZE = 2048;
203:
204: /**
205: * Constructs a <code>MessagePart</code> object from an
206: * <code>InputStream</code>. The contents of the <code>MessagePart</code>
207: * are loaded from the <code>InputStream</code> during the constructor
208: * call until the end of the stream is reached.
209: * @param is <code>InputStream</code> from which the contents of the
210: * <code>MessagePart</code> are read.
211: * @param mimeType the MIME Content-Type for the <code>MessagePart</code>
212: * [RFC 2046]
213: * @param contentId the content-id header field value for the
214: * <code>MessagePart</code> [RFC 2045]. The content-id is unique
215: * over all <code>MessagePart</code>s of a
216: * <code>MultipartMessage</code> and must always be set for each
217: * message part.
218: * @param contentLocation the content location which specifies the
219: * file name of the file that is attached. If the content location is
220: * set to <code>null</code> no content location will be set for this
221: * <code>MessagePart</code>.
222: * @param enc the encoding scheme for the <code>MessagePart</code>.
223: * if <code>enc</code> is set to <code>null</code> no encoding will
224: * be used for this <code>MessagePart</code>.
225: * @throws java.io.IOException if reading the <code>InputStream</code>
226: * causes an exception other than <code>EOFException</code>.
227: * @throws java.lang.IllegalArgumentException if mimeType or contentId is
228: * <code>null</code>. This exception will be thrown if
229: * <code>contentID</code> or <code>contentLocation</code> contains
230: * other characters than specified in US-ASCII format or if
231: * the specified encoding scheme is unknown.
232: * @throws SizeExceededException of the content from the
233: * <code>InputStream</code> is larger than the available memory or
234: * supported size for the message part.
235: */
236: public MessagePart(java.io.InputStream is,
237: java.lang.String mimeType, java.lang.String contentId,
238: java.lang.String contentLocation, java.lang.String enc)
239: throws IOException, SizeExceededException {
240: byte[] bytes = {};
241: if (is != null) {
242: ByteArrayOutputStream accumulator = new ByteArrayOutputStream();
243: byte[] buffer = new byte[BUFFER_SIZE];
244: int readBytes = 0;
245: while ((readBytes = is.read(buffer)) != -1) {
246: accumulator.write(buffer, 0, readBytes);
247: }
248: bytes = accumulator.toByteArray();
249: }
250: construct(bytes, 0, bytes.length, mimeType, contentId,
251: contentLocation, enc);
252: }
253:
254: /**
255: * Returns the content of the <code>MessagePart</code> as an array of
256: * bytes. If it's not possible to create an arary which can contain all
257: * data, this method must throw an <code>OutOfMemoryError</code>.
258: * @return <code>MessagePart</code> data as byte array
259: */
260: public byte[] getContent() {
261: if (content == null) {
262: return null;
263: }
264: byte[] copyOfContent = new byte[content.length];
265: System.arraycopy(content, 0, copyOfContent, 0, content.length);
266: return copyOfContent;
267: }
268:
269: /**
270: * Returns an <code>InputStream</code> for reading the contents of the
271: * <code>MessagePart</code>. Returns an empty stream if no content is
272: * available.
273: * @return an <code>InputStream</code> that can be used for reading the
274: * contents of this <code>MessagePart</code>.
275: */
276: public java.io.InputStream getContentAsStream() {
277: if (content == null) {
278: return new ByteArrayInputStream(new byte[0]);
279: } else {
280: return new ByteArrayInputStream(content);
281: }
282: }
283:
284: /**
285: * Returns the content-id value of the <code>MessagePart</code>.
286: * @return the value of the content-id as a String, or <code>null</code>
287: * if the content-id is not set (possible if the message was sent
288: * from a not JSR 205 compliant client).
289: */
290: public java.lang.String getContentID() {
291: return contentID;
292: }
293:
294: /**
295: * Returns content location of the <code>MessagePart</code>.
296: * @return content location
297: */
298: public java.lang.String getContentLocation() {
299: return contentLocation;
300: }
301:
302: /**
303: * Returns the encoding of the content, e.g. "US-ASCII", "UTF-8",
304: * "UTF-16", ... as a <code>String</code>.
305: * @return the encoding of the <code>MessagePart</code> content or
306: * </code>null</code> if the encoding scheme of the
307: * <code>MessagePart</code> cannot be determined.
308: */
309: public java.lang.String getEncoding() {
310: return encoding;
311: }
312:
313: /**
314: * Returns the content size of this <code>MessagePart</code>.
315: * @return Content size (in bytes) of this <code>MessagePart</code> or 0 if
316: * the <code>MessagePart</code> is empty.
317: */
318: public int getLength() {
319: return content == null ? 0 : content.length;
320: }
321:
322: /**
323: * Returns the mime type of the <code>MessagePart</code>.
324: * @return MIME type of the <code>MessagePart</code>.
325: */
326: public java.lang.String getMIMEType() {
327: return mimeType;
328: }
329:
330: /** Content byte array. */
331: byte[] content;
332: /** MIME Content ID. */
333: String contentID;
334: /** Content location. */
335: String contentLocation;
336: /** Content encoding. */
337: String encoding;
338: /** MIME type. */
339: String mimeType;
340:
341: /**
342: * Verifies the content identifier.
343: * @param contentId content id to be checked
344: * @exception IllegalArgumentException if content id is not valid
345: */
346: static void checkContentID(String contentId)
347: throws IllegalArgumentException {
348: if (contentId == null) {
349: throw new IllegalArgumentException(
350: "contentId must be specified");
351: }
352: if (contentId.length() > 100) { // MMS Conformance limit
353: throw new IllegalArgumentException(
354: "contentId exceeds 100 char limit");
355: }
356: if (containsNonUSASCII(contentId)) {
357: throw new IllegalArgumentException(
358: "contentId must not contain non-US-ASCII characters");
359: }
360: }
361:
362: /**
363: * Verifies the content location.
364: * @param contentLoc content location to be checked.
365: * @exception IllegalArgumentException if content location is not valid.
366: */
367: static void checkContentLocation(String contentLoc)
368: throws IllegalArgumentException {
369: if (contentLoc != null) {
370: if (containsNonUSASCII(contentLoc)) {
371: throw new IllegalArgumentException(
372: "contentLocation must not contain non-US-ASCII characters");
373: }
374: if (contentLoc.length() > 100) { // MMS Conformance limit
375: throw new IllegalArgumentException(
376: "contentLocation exceeds 100 char limit");
377: }
378: }
379: }
380:
381: /**
382: * Verifies the content encoding.
383: * @param encoding The content encoding to be checked.
384: * @exception IllegalArgumentException if content encoding is not valid.
385: */
386: static void checkEncodingScheme(String encoding)
387: throws IllegalArgumentException {
388: // IMPL_NOTE: check for a valid encoding scheme
389: }
390:
391: /** Lowest valid ASCII character. */
392: static final char US_ASCII_LOWEST_VALID_CHAR = 32;
393:
394: /** Mask for ASCII character checks. */
395: static final char US_ASCII_VALID_BIT_MASK = 0x7F;
396:
397: /**
398: * Checks if a string contains non-ASCII characters.
399: * @param str Text to be checked.
400: * @return <code>true</code> if non-ASCII characters are found.
401: */
402: static boolean containsNonUSASCII(String str) {
403: int numChars = str.length();
404: for (int i = 0; i < numChars; ++i) {
405: char this Char = str.charAt(i);
406: if (this Char < US_ASCII_LOWEST_VALID_CHAR
407: || this Char != (this Char & US_ASCII_VALID_BIT_MASK))
408: return true;
409: }
410: return false;
411: }
412: }
|