001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.core;
019:
020: import org.apache.james.util.InternetPrintWriter;
021: import org.apache.james.util.io.IOUtil;
022:
023: import javax.activation.UnsupportedDataTypeException;
024: import javax.mail.MessagingException;
025: import javax.mail.internet.MimeMessage;
026: import javax.mail.internet.MimeUtility;
027:
028: import java.io.BufferedWriter;
029: import java.io.ByteArrayInputStream;
030: import java.io.ByteArrayOutputStream;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.io.OutputStream;
034: import java.io.OutputStreamWriter;
035: import java.io.PrintWriter;
036: import java.util.Enumeration;
037:
038: /**
039: * Utility class to provide optimized write methods for the various MimeMessage
040: * implementations.
041: */
042: public class MimeMessageUtil {
043:
044: /**
045: * Convenience method to take any MimeMessage and write the headers and body to two
046: * different output streams
047: */
048: public static void writeTo(MimeMessage message,
049: OutputStream headerOs, OutputStream bodyOs)
050: throws IOException, MessagingException {
051: writeTo(message, headerOs, bodyOs, null);
052: }
053:
054: /**
055: * Convenience method to take any MimeMessage and write the headers and body to two
056: * different output streams, with an ignore list
057: */
058: public static void writeTo(MimeMessage message,
059: OutputStream headerOs, OutputStream bodyOs,
060: String[] ignoreList) throws IOException, MessagingException {
061: MimeMessage testMessage = message;
062: if (message instanceof MimeMessageCopyOnWriteProxy) {
063: MimeMessageCopyOnWriteProxy wr = (MimeMessageCopyOnWriteProxy) message;
064: testMessage = wr.getWrappedMessage();
065: }
066: if (testMessage instanceof MimeMessageWrapper) {
067: MimeMessageWrapper wrapper = (MimeMessageWrapper) testMessage;
068: if (!wrapper.isModified()) {
069: wrapper.writeTo(headerOs, bodyOs, ignoreList);
070: return;
071: }
072: }
073: writeToInternal(message, headerOs, bodyOs, ignoreList);
074: }
075:
076: /**
077: * @param message
078: * @param headerOs
079: * @param bodyOs
080: * @param ignoreList
081: * @throws MessagingException
082: * @throws IOException
083: * @throws UnsupportedDataTypeException
084: */
085: public static void writeToInternal(MimeMessage message,
086: OutputStream headerOs, OutputStream bodyOs,
087: String[] ignoreList) throws MessagingException,
088: IOException, UnsupportedDataTypeException {
089: if (message.getMessageID() == null) {
090: message.saveChanges();
091: }
092:
093: writeHeadersTo(message, headerOs, ignoreList);
094:
095: // Write the body to the output stream
096: writeMessageBodyTo(message, bodyOs);
097: }
098:
099: public static void writeMessageBodyTo(MimeMessage message,
100: OutputStream bodyOs) throws IOException,
101: UnsupportedDataTypeException, MessagingException {
102: OutputStream bos;
103: InputStream bis;
104:
105: try {
106: // Get the message as a stream. This will encode
107: // objects as necessary, and we have some input from
108: // decoding an re-encoding the stream. I'd prefer the
109: // raw stream, but see
110: bos = MimeUtility.encode(bodyOs, message.getEncoding());
111: bis = message.getInputStream();
112: } catch (UnsupportedDataTypeException udte) {
113: /* If we get an UnsupportedDataTypeException try using
114: * the raw input stream as a "best attempt" at rendering
115: * a message.
116: *
117: * WARNING: JavaMail v1.3 getRawInputStream() returns
118: * INVALID (unchanged) content for a changed message.
119: * getInputStream() works properly, but in this case
120: * has failed due to a missing DataHandler.
121: *
122: * MimeMessage.getRawInputStream() may throw a "no
123: * content" MessagingException. In JavaMail v1.3, when
124: * you initially create a message using MimeMessage
125: * APIs, there is no raw content available.
126: * getInputStream() works, but getRawInputStream()
127: * throws an exception. If we catch that exception,
128: * throw the UDTE. It should mean that someone has
129: * locally constructed a message part for which JavaMail
130: * doesn't have a DataHandler.
131: */
132:
133: try {
134: bis = message.getRawInputStream();
135: bos = bodyOs;
136: } catch (javax.mail.MessagingException _) {
137: throw udte;
138: }
139: } catch (javax.mail.MessagingException me) {
140: /* This could be another kind of MessagingException
141: * thrown by MimeMessage.getInputStream(), such as a
142: * javax.mail.internet.ParseException.
143: *
144: * The ParseException is precisely one of the reasons
145: * why the getRawInputStream() method exists, so that we
146: * can continue to stream the content, even if we cannot
147: * handle it. Again, if we get an exception, we throw
148: * the one that caused us to call getRawInputStream().
149: */
150: try {
151: bis = message.getRawInputStream();
152: bos = bodyOs;
153: } catch (javax.mail.MessagingException _) {
154: throw me;
155: }
156: }
157:
158: try {
159: copyStream(bis, bos);
160: } finally {
161: IOUtil.shutdownStream(bis);
162: }
163: }
164:
165: /**
166: * Convenience method to copy streams
167: */
168: public static void copyStream(InputStream in, OutputStream out)
169: throws IOException {
170: // TODO: This is really a bad way to do this sort of thing. A shared buffer to
171: // allow simultaneous read/writes would be a substantial improvement
172: byte[] block = new byte[1024];
173: int read = 0;
174: while ((read = in.read(block)) > -1) {
175: out.write(block, 0, read);
176: }
177: out.flush();
178: }
179:
180: /**
181: * Write the message headers to the given outputstream
182: *
183: * @param message
184: * @param headerOs
185: * @param ignoreList
186: * @throws MessagingException
187: */
188: private static void writeHeadersTo(MimeMessage message,
189: OutputStream headerOs, String[] ignoreList)
190: throws MessagingException {
191: //Write the headers (minus ignored ones)
192: Enumeration headers = message
193: .getNonMatchingHeaderLines(ignoreList);
194: writeHeadersTo(headers, headerOs);
195: }
196:
197: /**
198: * Write the message headers to the given outputstream
199: *
200: * @param message
201: * @param headerOs
202: * @param ignoreList
203: * @throws MessagingException
204: */
205: public static void writeHeadersTo(Enumeration headers,
206: OutputStream headerOs) throws MessagingException {
207: PrintWriter hos = new InternetPrintWriter(new BufferedWriter(
208: new OutputStreamWriter(headerOs), 512), true);
209: while (headers.hasMoreElements()) {
210: hos.println((String) headers.nextElement());
211: }
212: // Print header/data separator
213: hos.println();
214: hos.flush();
215: }
216:
217: /**
218: * @param message
219: * @param ignoreList
220: * @return
221: * @throws MessagingException
222: */
223: public static InputStream getHeadersInputStream(
224: MimeMessage message, String[] ignoreList)
225: throws MessagingException {
226: ByteArrayOutputStream bo = new ByteArrayOutputStream();
227: writeHeadersTo(message, bo, ignoreList);
228: return new ByteArrayInputStream(bo.toByteArray());
229: }
230:
231: /**
232: * Slow method to calculate the exact size of a message!
233: */
234: private static final class SizeCalculatorOutputStream extends
235: OutputStream {
236: long size = 0;
237:
238: public void write(int arg0) throws IOException {
239: size++;
240: }
241:
242: public long getSize() {
243: return size;
244: }
245:
246: public void write(byte[] arg0, int arg1, int arg2)
247: throws IOException {
248: size += arg2;
249: }
250:
251: public void write(byte[] arg0) throws IOException {
252: size += arg0.length;
253: }
254: }
255:
256: /**
257: * @return size of full message including headers
258: *
259: * @throws MessagingException if a problem occours while computing the message size
260: */
261: public static long getMessageSize(MimeMessage message)
262: throws MessagingException {
263: //If we have a MimeMessageWrapper, then we can ask it for just the
264: // message size and skip calculating it
265: long size = -1;
266:
267: if (message instanceof MimeMessageWrapper) {
268: MimeMessageWrapper wrapper = (MimeMessageWrapper) message;
269: size = wrapper.getMessageSize();
270: } else if (message instanceof MimeMessageCopyOnWriteProxy) {
271: MimeMessageCopyOnWriteProxy wrapper = (MimeMessageCopyOnWriteProxy) message;
272: size = wrapper.getMessageSize();
273: }
274:
275: if (size == -1) {
276: size = calculateMessageSize(message);
277: }
278:
279: return size;
280: }
281:
282: /**
283: * @param message
284: * @return the calculated size
285: * @throws MessagingException
286: */
287: public static long calculateMessageSize(MimeMessage message)
288: throws MessagingException {
289: long size;
290: //SK: Should probably eventually store this as a locally
291: // maintained value (so we don't have to load and reparse
292: // messages each time).
293: size = message.getSize();
294: if (size != -1) {
295: Enumeration e = message.getAllHeaderLines();
296: if (e.hasMoreElements()) {
297: size += 2;
298: }
299: while (e.hasMoreElements()) {
300: // add 2 bytes for the CRLF
301: size += ((String) e.nextElement()).length() + 2;
302: }
303: }
304:
305: if (size == -1) {
306: SizeCalculatorOutputStream out = new SizeCalculatorOutputStream();
307: try {
308: message.writeTo(out);
309: } catch (IOException e) {
310: // should never happen as SizeCalculator does not actually throw IOExceptions.
311: throw new MessagingException(
312: "IOException wrapped by getMessageSize", e);
313: }
314: size = out.getSize();
315: }
316: return size;
317: }
318:
319: }
|