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