View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  package org.apache.james.core;
18  
19  import org.apache.james.util.InternetPrintWriter;
20  import org.apache.james.util.io.IOUtil;
21  
22  import javax.activation.UnsupportedDataTypeException;
23  import javax.mail.MessagingException;
24  import javax.mail.internet.MimeMessage;
25  import javax.mail.internet.MimeUtility;
26  
27  import java.io.BufferedWriter;
28  import java.io.ByteArrayInputStream;
29  import java.io.ByteArrayOutputStream;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.io.OutputStreamWriter;
34  import java.io.PrintWriter;
35  import java.util.Enumeration;
36  
37  /***
38   * Utility class to provide optimized write methods for the various MimeMessage
39   * implementations.
40   */
41  public class MimeMessageUtil {
42  
43      /***
44       * Convenience method to take any MimeMessage and write the headers and body to two
45       * different output streams
46       */
47      public static void writeTo(MimeMessage message, OutputStream headerOs, OutputStream bodyOs) throws IOException, MessagingException {
48          writeTo(message, headerOs, bodyOs, null);
49      }
50  
51      /***
52       * Convenience method to take any MimeMessage and write the headers and body to two
53       * different output streams, with an ignore list
54       */
55      public static void writeTo(MimeMessage message, OutputStream headerOs, OutputStream bodyOs, String[] ignoreList) throws IOException, MessagingException {
56          MimeMessage testMessage = message;
57          if (message instanceof MimeMessageCopyOnWriteProxy) {
58              MimeMessageCopyOnWriteProxy wr = (MimeMessageCopyOnWriteProxy) message;
59              testMessage = wr.getWrappedMessage();
60          }
61          if (testMessage instanceof MimeMessageWrapper) {
62              MimeMessageWrapper wrapper = (MimeMessageWrapper)testMessage;
63              if (!wrapper.isModified()) {
64                  wrapper.writeTo(headerOs, bodyOs, ignoreList);
65                  return;
66              }
67          }
68          writeToInternal(message, headerOs, bodyOs, ignoreList);
69      }
70  
71      /***
72       * @param message
73       * @param headerOs
74       * @param bodyOs
75       * @param ignoreList
76       * @throws MessagingException
77       * @throws IOException
78       * @throws UnsupportedDataTypeException
79       */
80      public static void writeToInternal(MimeMessage message, OutputStream headerOs, OutputStream bodyOs, String[] ignoreList) throws MessagingException, IOException, UnsupportedDataTypeException {
81          if(message.getMessageID() == null) {
82              message.saveChanges();
83          }
84  
85          writeHeadersTo(message, headerOs, ignoreList);
86  
87          // Write the body to the output stream
88          writeMessageBodyTo(message, bodyOs);
89      }
90  
91      public static void writeMessageBodyTo(MimeMessage message, OutputStream bodyOs) throws IOException, UnsupportedDataTypeException, MessagingException {
92          OutputStream bos;
93          InputStream bis;
94  
95          try {
96              // Get the message as a stream.  This will encode
97              // objects as necessary, and we have some input from
98              // decoding an re-encoding the stream.  I'd prefer the
99              // raw stream, but see
100             bos = MimeUtility.encode(bodyOs, message.getEncoding());
101             bis = message.getInputStream();
102         } catch(UnsupportedDataTypeException udte) {
103             /* If we get an UnsupportedDataTypeException try using
104              * the raw input stream as a "best attempt" at rendering
105              * a message.
106              *
107              * WARNING: JavaMail v1.3 getRawInputStream() returns
108              * INVALID (unchanged) content for a changed message.
109              * getInputStream() works properly, but in this case
110              * has failed due to a missing DataHandler.
111              *
112              * MimeMessage.getRawInputStream() may throw a "no
113              * content" MessagingException.  In JavaMail v1.3, when
114              * you initially create a message using MimeMessage
115              * APIs, there is no raw content available.
116              * getInputStream() works, but getRawInputStream()
117              * throws an exception.  If we catch that exception,
118              * throw the UDTE.  It should mean that someone has
119              * locally constructed a message part for which JavaMail
120              * doesn't have a DataHandler.
121             */
122 
123             try {
124                 bis = message.getRawInputStream();
125                 bos = bodyOs;
126             } catch(javax.mail.MessagingException _) {
127                 throw udte;
128             }
129         }
130         catch(javax.mail.MessagingException me) {
131             /* This could be another kind of MessagingException
132              * thrown by MimeMessage.getInputStream(), such as a
133              * javax.mail.internet.ParseException.
134              *
135              * The ParseException is precisely one of the reasons
136              * why the getRawInputStream() method exists, so that we
137              * can continue to stream the content, even if we cannot
138              * handle it.  Again, if we get an exception, we throw
139              * the one that caused us to call getRawInputStream().
140              */
141             try {
142                 bis = message.getRawInputStream();
143                 bos = bodyOs;
144             } catch(javax.mail.MessagingException _) {
145                 throw me;
146             }
147         }
148 
149         try {
150             copyStream(bis, bos);
151         }
152         finally {
153             IOUtil.shutdownStream(bis);
154         }
155     }
156 
157     /***
158      * Convenience method to copy streams
159      */
160     public static void copyStream(InputStream in, OutputStream out) throws IOException {
161         // TODO: This is really a bad way to do this sort of thing.  A shared buffer to
162         //       allow simultaneous read/writes would be a substantial improvement
163         byte[] block = new byte[1024];
164         int read = 0;
165         while ((read = in.read(block)) > -1) {
166             out.write(block, 0, read);
167         }
168         out.flush();
169     }
170 
171 
172     /***
173      * Write the message headers to the given outputstream
174      * 
175      * @param message
176      * @param headerOs
177      * @param ignoreList
178      * @throws MessagingException
179      */
180     private static void writeHeadersTo(MimeMessage message, OutputStream headerOs, String[] ignoreList) throws MessagingException {
181         //Write the headers (minus ignored ones)
182         Enumeration headers = message.getNonMatchingHeaderLines(ignoreList);
183         writeHeadersTo(headers, headerOs);
184     }
185 
186     /***
187      * Write the message headers to the given outputstream
188      * 
189      * @param message
190      * @param headerOs
191      * @param ignoreList
192      * @throws MessagingException
193      */
194     public static void writeHeadersTo(Enumeration headers, OutputStream headerOs) throws MessagingException {
195         PrintWriter hos = new InternetPrintWriter(new BufferedWriter(new OutputStreamWriter(headerOs), 512), true);
196         while (headers.hasMoreElements()) {
197             hos.println((String)headers.nextElement());
198         }
199         // Print header/data separator
200         hos.println();
201         hos.flush();
202     }
203     
204     /***
205      * @param message
206      * @param ignoreList
207      * @return
208      * @throws MessagingException
209      */
210     public static InputStream getHeadersInputStream(MimeMessage message, String[] ignoreList) throws MessagingException {
211         ByteArrayOutputStream bo = new ByteArrayOutputStream();
212         writeHeadersTo(message,bo,ignoreList);
213         return new ByteArrayInputStream(bo.toByteArray());
214     }
215 
216 
217     /***
218      * Slow method to calculate the exact size of a message!
219      */
220     private static final class SizeCalculatorOutputStream extends OutputStream {
221         long size = 0;
222 
223         public void write(int arg0) throws IOException {
224             size++;
225         }
226 
227         public long getSize() {
228             return size;
229         }
230 
231         public void write(byte[] arg0, int arg1, int arg2) throws IOException {
232             size += arg2;
233         }
234 
235         public void write(byte[] arg0) throws IOException {
236             size += arg0.length;
237         }
238     }
239 
240     /***
241      * @return size of full message including headers
242      * 
243      * @throws MessagingException if a problem occours while computing the message size
244      */
245     public static long getMessageSize(MimeMessage message) throws MessagingException {
246         //If we have a MimeMessageWrapper, then we can ask it for just the
247         //  message size and skip calculating it
248         long size = -1;
249         
250         if (message instanceof MimeMessageWrapper) {
251             MimeMessageWrapper wrapper = (MimeMessageWrapper) message;
252             size = wrapper.getMessageSize();
253         } else if (message instanceof MimeMessageCopyOnWriteProxy) {
254             MimeMessageCopyOnWriteProxy wrapper = (MimeMessageCopyOnWriteProxy) message;
255             size = wrapper.getMessageSize();
256         }
257         
258         if (size == -1) {
259             size = calculateMessageSize(message);
260         }
261         
262         return size;
263     }
264 
265     /***
266      * @param message
267      * @return the calculated size
268      * @throws MessagingException
269      */
270     public static long calculateMessageSize(MimeMessage message) throws MessagingException {
271         long size;
272         //SK: Should probably eventually store this as a locally
273         //  maintained value (so we don't have to load and reparse
274         //  messages each time).
275         size = message.getSize();
276         if (size != -1) {
277             Enumeration e = message.getAllHeaderLines();
278             if (e.hasMoreElements()) {
279                 size += 2;
280             }
281             while (e.hasMoreElements()) {
282                 // add 2 bytes for the CRLF
283                 size += ((String) e.nextElement()).length()+2;
284             }
285         }
286         
287         
288         if (size == -1) {
289             SizeCalculatorOutputStream out = new SizeCalculatorOutputStream();
290             try {
291                 message.writeTo(out);
292             } catch (IOException e) {
293                 // should never happen as SizeCalculator does not actually throw IOExceptions.
294                 throw new MessagingException("IOException wrapped by getMessageSize",e);
295             }
296             size = out.getSize();
297         }
298         return size;
299     }
300     
301 
302 }