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
18 package org.apache.james.core;
19
20 import org.apache.avalon.framework.activity.Disposable;
21 import org.apache.avalon.framework.container.ContainerUtil;
22 import org.apache.james.util.InternetPrintWriter;
23 import org.apache.james.util.io.IOUtil;
24
25 import javax.activation.DataHandler;
26 import javax.mail.MessagingException;
27 import javax.mail.Session;
28 import javax.mail.internet.InternetHeaders;
29 import javax.mail.internet.MimeMessage;
30 import javax.mail.util.SharedByteArrayInputStream;
31
32 import java.io.BufferedWriter;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.io.LineNumberReader;
38 import java.io.OutputStream;
39 import java.io.OutputStreamWriter;
40 import java.io.PrintWriter;
41 import java.util.Enumeration;
42
43 /***
44 * This object wraps a MimeMessage, only loading the underlying MimeMessage
45 * object when needed. Also tracks if changes were made to reduce
46 * unnecessary saves.
47 */
48 public class MimeMessageWrapper
49 extends MimeMessage
50 implements Disposable {
51
52 /***
53 * Can provide an input stream to the data
54 */
55 protected MimeMessageSource source = null;
56
57 /***
58 * This is false until we parse the message
59 */
60 protected boolean messageParsed = false;
61
62 /***
63 * This is false until we parse the message
64 */
65 protected boolean headersModified = false;
66
67 /***
68 * This is false until we parse the message
69 */
70 protected boolean bodyModified = false;
71
72 /***
73 * Keep a reference to the sourceIn so we can close it
74 * only when we dispose the message.
75 */
76 private InputStream sourceIn;
77
78 private MimeMessageWrapper(Session session) throws MessagingException {
79 super(session);
80 this.headers = null;
81 this.modified = false;
82 this.headersModified = false;
83 this.bodyModified = false;
84 }
85
86 /***
87 * A constructor that instantiates a MimeMessageWrapper based on
88 * a MimeMessageSource
89 *
90 * @param source the MimeMessageSource
91 * @throws MessagingException
92 */
93 public MimeMessageWrapper(Session session, MimeMessageSource source) throws MessagingException {
94 this(session);
95 this.source = source;
96 }
97
98 /***
99 * A constructor that instantiates a MimeMessageWrapper based on
100 * a MimeMessageSource
101 *
102 * @param source the MimeMessageSource
103 * @throws MessagingException
104 * @throws MessagingException
105 */
106 public MimeMessageWrapper(MimeMessageSource source) throws MessagingException {
107 this(Session.getDefaultInstance(System.getProperties()),source);
108 }
109
110 public MimeMessageWrapper(MimeMessage original) throws MessagingException {
111 this(Session.getDefaultInstance(System.getProperties()));
112 flags = original.getFlags();
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 if (source == null) {
131 ByteArrayOutputStream bos;
132 int size = original.getSize();
133 if (size > 0)
134 bos = new ByteArrayOutputStream(size);
135 else
136 bos = new ByteArrayOutputStream();
137 try {
138 original.writeTo(bos);
139 bos.close();
140 SharedByteArrayInputStream bis =
141 new SharedByteArrayInputStream(bos.toByteArray());
142 parse(bis);
143 bis.close();
144 saved = true;
145 } catch (IOException ex) {
146
147 throw new MessagingException("IOException while copying message",
148 ex);
149 }
150 }
151 }
152
153 /***
154 * Returns the source ID of the MimeMessageSource that is supplying this
155 * with data.
156 * @see MimeMessageSource
157 */
158 public synchronized String getSourceId() {
159 return source != null ? source.getSourceId() : null;
160 }
161
162 /***
163 * Load the message headers from the internal source.
164 *
165 * @throws MessagingException if an error is encountered while
166 * loading the headers
167 */
168 protected synchronized void loadHeaders() throws MessagingException {
169 if (headers != null) {
170
171 return;
172 } else if (source != null) {
173 try {
174 InputStream in = source.getInputStream();
175 try {
176 headers = createInternetHeaders(in);
177 } finally {
178 IOUtil.shutdownStream(in);
179 }
180 } catch (IOException ioe) {
181 throw new MessagingException("Unable to parse headers from stream: " + ioe.getMessage(), ioe);
182 }
183 } else {
184 throw new MessagingException("loadHeaders called for a message with no source, contentStream or stream");
185 }
186 }
187
188 /***
189 * Load the complete MimeMessage from the internal source.
190 *
191 * @throws MessagingException if an error is encountered while
192 * loading the message
193 */
194 protected synchronized void loadMessage() throws MessagingException {
195 if (messageParsed) {
196
197 return;
198 } else if (source != null) {
199 sourceIn = null;
200 try {
201 sourceIn = source.getInputStream();
202
203 parse(sourceIn);
204
205 saved = true;
206
207 } catch (IOException ioe) {
208 IOUtil.shutdownStream(sourceIn);
209 sourceIn = null;
210 throw new MessagingException("Unable to parse stream: " + ioe.getMessage(), ioe);
211 }
212 } else {
213 throw new MessagingException("loadHeaders called for an unparsed message with no source");
214 }
215 }
216
217 /***
218 * Get whether the message has been modified.
219 *
220 * @return whether the message has been modified
221 */
222 public synchronized boolean isModified() {
223 return headersModified || bodyModified || modified;
224 }
225
226 /***
227 * Rewritten for optimization purposes
228 */
229 public synchronized void writeTo(OutputStream os) throws IOException, MessagingException {
230 if (source != null && !isModified()) {
231
232
233 InputStream in = source.getInputStream();
234 try {
235 MimeMessageUtil.copyStream(in, os);
236 } finally {
237 IOUtil.shutdownStream(in);
238 }
239 } else {
240 writeTo(os, os);
241 }
242 }
243
244 /***
245 * Rewritten for optimization purposes
246 */
247 public void writeTo(OutputStream os, String[] ignoreList) throws IOException, MessagingException {
248 writeTo(os, os, ignoreList);
249 }
250
251 /***
252 * Write
253 */
254 public void writeTo(OutputStream headerOs, OutputStream bodyOs) throws IOException, MessagingException {
255 writeTo(headerOs, bodyOs, new String[0]);
256 }
257
258 public synchronized void writeTo(OutputStream headerOs, OutputStream bodyOs, String[] ignoreList) throws IOException, MessagingException {
259 if (source != null && !isModified()) {
260
261
262
263
264 InputStream in = source.getInputStream();
265 try {
266 InternetHeaders headers = new InternetHeaders(in);
267 PrintWriter pos = new InternetPrintWriter(new BufferedWriter(new OutputStreamWriter(headerOs), 512), true);
268 for (Enumeration e = headers.getNonMatchingHeaderLines(ignoreList); e.hasMoreElements(); ) {
269 String header = (String)e.nextElement();
270 pos.println(header);
271 }
272 pos.println();
273 pos.flush();
274 MimeMessageUtil.copyStream(in, bodyOs);
275 } finally {
276 IOUtil.shutdownStream(in);
277 }
278 } else {
279 MimeMessageUtil.writeToInternal(this, headerOs, bodyOs, ignoreList);
280 }
281 }
282
283 /***
284 * This is the MimeMessage implementation - this should return ONLY the
285 * body, not the entire message (should not count headers). Will have
286 * to parse the message.
287 */
288 public int getSize() throws MessagingException {
289 if (!messageParsed) {
290 loadMessage();
291 }
292 return super.getSize();
293 }
294
295 /***
296 * Corrects JavaMail 1.1 version which always returns -1.
297 * Only corrected for content less than 5000 bytes,
298 * to avoid memory hogging.
299 */
300 public int getLineCount() throws MessagingException {
301 InputStream in=null;
302 try{
303 in = getContentStream();
304 }catch(Exception e){
305 return -1;
306 }
307 if (in == null) {
308 return -1;
309 }
310
311
312 try {
313 LineNumberReader counter;
314 if (getEncoding() != null) {
315 counter = new LineNumberReader(new InputStreamReader(in, getEncoding()));
316 } else {
317 counter = new LineNumberReader(new InputStreamReader(in));
318 }
319
320 char[] block = new char[4096];
321 while (counter.read(block) > -1) {
322
323 }
324 return counter.getLineNumber();
325 } catch (IOException ioe) {
326 return -1;
327 } finally {
328 IOUtil.shutdownStream(in);
329 }
330 }
331
332 /***
333 * Returns size of message, ie headers and content
334 */
335 public long getMessageSize() throws MessagingException {
336 if (source != null && !isModified()) {
337 try {
338 return source.getMessageSize();
339 } catch (IOException ioe) {
340 throw new MessagingException("Error retrieving message size", ioe);
341 }
342 } else {
343 return MimeMessageUtil.calculateMessageSize(this);
344 }
345 }
346
347 /***
348 * We override all the "headers" access methods to be sure that we
349 * loaded the headers
350 */
351
352 public String[] getHeader(String name) throws MessagingException {
353 if (headers == null) {
354 loadHeaders();
355 }
356 return headers.getHeader(name);
357 }
358
359 public String getHeader(String name, String delimiter) throws MessagingException {
360 if (headers == null) {
361 loadHeaders();
362 }
363 return headers.getHeader(name, delimiter);
364 }
365
366 public Enumeration getAllHeaders() throws MessagingException {
367 if (headers == null) {
368 loadHeaders();
369 }
370 return headers.getAllHeaders();
371 }
372
373 public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
374 if (headers == null) {
375 loadHeaders();
376 }
377 return headers.getMatchingHeaders(names);
378 }
379
380 public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
381 if (headers == null) {
382 loadHeaders();
383 }
384 return headers.getNonMatchingHeaders(names);
385 }
386
387 public Enumeration getAllHeaderLines() throws MessagingException {
388 if (headers == null) {
389 loadHeaders();
390 }
391 return headers.getAllHeaderLines();
392 }
393
394 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
395 if (headers == null) {
396 loadHeaders();
397 }
398 return headers.getMatchingHeaderLines(names);
399 }
400
401 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
402 if (headers == null) {
403 loadHeaders();
404 }
405 return headers.getNonMatchingHeaderLines(names);
406 }
407
408
409 private synchronized void checkModifyHeaders() throws MessagingException {
410
411 if (!messageParsed) {
412 loadMessage();
413 }
414
415 if (headers == null) {
416 loadHeaders();
417 }
418 modified = true;
419 saved = false;
420 headersModified = true;
421 }
422
423 public void setHeader(String name, String value) throws MessagingException {
424 checkModifyHeaders();
425 super.setHeader(name, value);
426 }
427
428 public void addHeader(String name, String value) throws MessagingException {
429 checkModifyHeaders();
430 super.addHeader(name, value);
431 }
432
433 public void removeHeader(String name) throws MessagingException {
434 checkModifyHeaders();
435 super.removeHeader(name);
436 }
437
438 public void addHeaderLine(String line) throws MessagingException {
439 checkModifyHeaders();
440 super.addHeaderLine(line);
441 }
442
443
444 /***
445 * The message is changed when working with headers and when altering the content.
446 * Every method that alter the content will fallback to this one.
447 *
448 * @see javax.mail.Part#setDataHandler(javax.activation.DataHandler)
449 */
450 public synchronized void setDataHandler(DataHandler arg0) throws MessagingException {
451 modified = true;
452 saved = false;
453 bodyModified = true;
454 super.setDataHandler(arg0);
455 }
456
457 /***
458 * @see org.apache.avalon.framework.activity.Disposable#dispose()
459 */
460 public void dispose() {
461 if (sourceIn != null) {
462 IOUtil.shutdownStream(sourceIn);
463 }
464 if (source != null) {
465 ContainerUtil.dispose(source);
466 }
467 }
468
469 /***
470 * @see javax.mail.internet.MimeMessage#parse(java.io.InputStream)
471 */
472 protected synchronized void parse(InputStream is) throws MessagingException {
473
474
475 super.parse(is);
476 messageParsed = true;
477 }
478
479 /***
480 * If we already parsed the headers then we simply return the updated ones.
481 * Otherwise we parse
482 *
483 * @see javax.mail.internet.MimeMessage#createInternetHeaders(java.io.InputStream)
484 */
485 protected synchronized InternetHeaders createInternetHeaders(InputStream is) throws MessagingException {
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509 MailHeaders newHeaders = new MailHeaders(is);
510
511 if (headers != null) {
512 return headers;
513 } else {
514 return newHeaders;
515 }
516 }
517
518 /***
519 * @see javax.mail.internet.MimeMessage#getContentStream()
520 */
521 protected InputStream getContentStream() throws MessagingException {
522 if (!messageParsed) {
523 loadMessage();
524 }
525 return super.getContentStream();
526 }
527
528 /***
529 * @see javax.mail.internet.MimeMessage#getRawInputStream()
530 */
531 public InputStream getRawInputStream() throws MessagingException {
532 if (!messageParsed && !isModified() && source != null) {
533 InputStream is;
534 try {
535 is = source.getInputStream();
536
537 new MailHeaders(is);
538 return is;
539 } catch (IOException e) {
540 throw new MessagingException("Unable to read the stream: " + e.getMessage(), e);
541 }
542 } else return super.getRawInputStream();
543 }
544
545
546
547 }