View Javadoc

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