KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > columba > mail > composer > MessageComposer


1 // The contents of this file are subject to the Mozilla Public License Version
2
// 1.1
3
//(the "License"); you may not use this file except in compliance with the
4
//License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
5
//
6
//Software distributed under the License is distributed on an "AS IS" basis,
7
//WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
8
//for the specific language governing rights and
9
//limitations under the License.
10
//
11
//The Original Code is "The Columba Project"
12
//
13
//The Initial Developers of the Original Code are Frederik Dietz and Timo
14
// Stich.
15
//Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
16
//
17
//All Rights Reserved.
18

19 package org.columba.mail.composer;
20
21 import java.io.BufferedReader JavaDoc;
22 import java.io.File JavaDoc;
23 import java.io.FileReader JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.InputStream JavaDoc;
26 import java.nio.charset.Charset JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.Date JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.logging.Logger JavaDoc;
32
33 import org.columba.api.command.IWorkerStatusController;
34 import org.columba.core.logging.Logging;
35 import org.columba.core.versioninfo.VersionInfo;
36 import org.columba.core.xml.XmlElement;
37 import org.columba.mail.config.AccountItem;
38 import org.columba.mail.config.Identity;
39 import org.columba.mail.config.MailConfig;
40 import org.columba.mail.config.SecurityItem;
41 import org.columba.mail.gui.composer.ComposerModel;
42 import org.columba.mail.message.PGPMimePart;
43 import org.columba.mail.message.SendableHeader;
44 import org.columba.mail.parser.ListBuilder;
45 import org.columba.mail.parser.ListParser;
46 import org.columba.mail.parser.text.HtmlParser;
47 import org.columba.ristretto.coder.EncodedWord;
48 import org.columba.ristretto.composer.MimeTreeRenderer;
49 import org.columba.ristretto.io.CharSequenceSource;
50 import org.columba.ristretto.message.Address;
51 import org.columba.ristretto.message.LocalMimePart;
52 import org.columba.ristretto.message.MessageDate;
53 import org.columba.ristretto.message.MessageIDGenerator;
54 import org.columba.ristretto.message.MimeHeader;
55 import org.columba.ristretto.message.MimePart;
56 import org.columba.ristretto.message.StreamableMimePart;
57 import org.columba.ristretto.parser.ParserException;
58
59 public class MessageComposer {
60     /** JDK 1.4+ logging framework logger, used for logging. */
61     private static final Logger JavaDoc LOG = Logger
62             .getLogger("org.columba.mail.composer");
63
64     private static final Charset JavaDoc headerCharset = Charset.forName("UTF-8");
65     
66     private ComposerModel model;
67
68     private int accountUid;
69
70     public MessageComposer(ComposerModel model) {
71         this.model = model;
72     }
73
74     protected SendableHeader initHeader() {
75         SendableHeader header = new SendableHeader();
76         
77         // RFC822 - Header
78
if (model.getToList() != null) {
79             String JavaDoc s = ListParser.createStringFromList(ListBuilder
80                     .createFlatList(model.getToList()), ",");
81             if ( s.length() != 0 )
82                 header.set("To",EncodedWord.encode(s,
83                         headerCharset, EncodedWord.QUOTED_PRINTABLE).toString() );
84         }
85
86         if (model.getCcList() != null) {
87             String JavaDoc s = ListParser.createStringFromList(ListBuilder
88                     .createFlatList(model.getCcList()), ",");
89             if ( s.length() != 0 )
90             header.set("Cc",EncodedWord.encode(s,
91                     headerCharset, EncodedWord.QUOTED_PRINTABLE).toString() );
92         }
93
94
95         header.getAttributes().put("columba.subject", model.getSubject());
96
97         //header.set("Subject",
98
// EncodedWord.encode(model.getSubject(),
99
// Charset.forName(model.getCharsetName()),
100
// EncodedWord.QUOTED_PRINTABLE).toString());
101
header.set("Subject", EncodedWord.encode(model.getSubject(),
102                 headerCharset, EncodedWord.QUOTED_PRINTABLE).toString());
103
104         AccountItem item = model.getAccountItem();
105         Identity identity = item.getIdentity();
106
107         //mod: 20040629 SWITT for redirecting feature
108
//If FROM value was set, take this as From, else take Identity
109
if (model.getMessage().getHeader().getHeader().get("From") != null) {
110             header.set("From", EncodedWord.encode(model.getMessage().getHeader().getHeader().get(
111                     "From"), headerCharset, EncodedWord.QUOTED_PRINTABLE).toString());
112         } else {
113             header.set("From", EncodedWord.encode(identity.getAddress().toString(),
114                     headerCharset, EncodedWord.QUOTED_PRINTABLE).toString());
115         }
116         
117         
118
119         header.set("X-Priority", model.getPriority());
120
121         /*
122          * String priority = controller.getModel().getPriority();
123          *
124          * if (priority != null) { header.set("columba.priority", new
125          * Integer(priority)); } else { header.set("columba.priority", new
126          * Integer(3)); }
127          */

128         header.set("Mime-Version", "1.0");
129
130         String JavaDoc organisation = identity.getOrganisation();
131
132         if (organisation != null && organisation.length() > 0) {
133             header.set("Organisation", organisation);
134         }
135
136         // reply-to
137
Address replyAddress = identity.getReplyToAddress();
138
139         if (replyAddress != null) {
140             header.set("Reply-To", EncodedWord.encode(replyAddress.getMailAddress(),
141                     headerCharset, EncodedWord.QUOTED_PRINTABLE).toString());
142         }
143
144         String JavaDoc messageID = MessageIDGenerator.generate();
145         header.set("Message-ID", messageID);
146
147         String JavaDoc inreply = model.getHeaderField("In-Reply-To");
148
149         if (inreply != null) {
150             header.set("In-Reply-To", EncodedWord.encode(inreply,
151                     headerCharset, EncodedWord.QUOTED_PRINTABLE).toString());
152         }
153
154         String JavaDoc references = model.getHeaderField("References");
155
156         if (references != null) {
157             header.set("References", references);
158         }
159
160         header.set("X-Mailer", "Columba ("
161                 + VersionInfo.getVersion() + ")");
162
163         header.getAttributes().put("columba.from", identity.getAddress());
164
165         // date
166
Date JavaDoc date = new Date JavaDoc();
167         header.getAttributes().put("columba.date", date);
168         header.set("Date", MessageDate.toString(date));
169
170         //attachments
171
header.getAttributes().put("columba.attachment", new Boolean JavaDoc(model.getAttachments().size() > 0));
172         
173         // copy flags
174
header.setFlags(model.getMessage().getHeader().getFlags());
175         
176         return header;
177     }
178
179     private boolean needQPEncoding(String JavaDoc input) {
180         for (int i = 0; i < input.length(); i++) {
181             if (input.charAt(i) > 127) {
182                 return true;
183             }
184         }
185
186         return false;
187     }
188
189     /**
190      * gives the signature for this Mail back. This signature is NOT a
191      * pgp-signature but a real mail-signature.
192      *
193      * @param item
194      * The item wich holds the signature-file
195      * @return The signature for the mail as a String. The Signature is
196      * character encoded with the caracter set from the model
197      */

198     protected String JavaDoc getSignature(File JavaDoc file) {
199         StringBuffer JavaDoc strbuf = new StringBuffer JavaDoc();
200
201         try {
202             BufferedReader JavaDoc in = new BufferedReader JavaDoc(new FileReader JavaDoc(file));
203
204             /*
205              * BufferedReader in = new BufferedReader( new InputStreamReader(
206              * new FileInputStream(file), model.getCharsetName()));
207              */

208             String JavaDoc str;
209
210             while ((str = in.readLine()) != null) {
211                 strbuf.append(str + "\n");
212             }
213
214             in.close();
215             
216             return strbuf.toString();
217         } catch (IOException JavaDoc ex) {
218             ex.printStackTrace();
219
220             return "";
221         }
222     }
223
224     /**
225      * Composes a multipart/alternative mime part for the body of a message
226      * containing a text part and a html part. <br>
227      * This is to be used for sending html messages, when an alternative text
228      * part - to be read by users not able to read html - is required. <br>
229      * Pre-condition: It is assumed that the model contains a message in html
230      * format.
231      *
232      * @return The composed mime part for the message body
233      * @author Karl Peder Olesen (karlpeder)
234      */

235     private StreamableMimePart composeMultipartAlternativeMimePart(boolean appendSignature) {
236         // compose text part
237
StreamableMimePart textPart = composeTextMimePart(appendSignature);
238
239         // compose html part
240
StreamableMimePart htmlPart = composeHtmlMimePart(appendSignature);
241
242         // merge mimeparts and return
243
LocalMimePart bodyPart = new LocalMimePart(new MimeHeader("multipart",
244                 "alternative"));
245         bodyPart.addChild(textPart);
246         bodyPart.addChild(htmlPart);
247
248         return bodyPart;
249     }
250
251     /**
252      * Composes a text/html mime part from the body contained in the composer
253      * model. This could be for a pure html message or for the html part of a
254      * multipart/alternative. <br>
255      * If a signature is defined, it is added to the body. <br>
256      * Pre-condition: It is assumed that the model contains a html message.
257      *
258      * @return The composed text/html mime part
259      * @author Karl Peder Olesen (karlpeder)
260      */

261     private StreamableMimePart composeHtmlMimePart(boolean appendSignature) {
262         // Init Mime-Header with Default-Values (text/html)
263
LocalMimePart bodyPart = new LocalMimePart(new MimeHeader("text",
264                 "html"));
265
266         // Set Default Charset or selected
267
String JavaDoc charsetName = model.getCharset().name();
268
269
270         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
271         String JavaDoc body = model.getBodyText();
272
273         // insert link tags for urls and email addresses
274
body = HtmlParser.substituteURL(body, false);
275         body = HtmlParser.substituteEmailAddress(body, false);
276
277         String JavaDoc lcase = body.toLowerCase(); // for text comparisons
278

279         // insert document type decl.
280
if (lcase.indexOf("<!doctype") == -1) {
281             // FIXME (@author karlpeder): Is 3.2 the proper version of html to refer to?
282
buf.append("<!DOCTYPE HTML PUBLIC "
283                     + "\"-//W3C//DTD HTML 3.2//EN\">\r\n");
284         }
285
286         // insert head section with charset def.
287
String JavaDoc meta = "<meta " + "http-equiv=\"Content-Type\" "
288                 + "content=\"text/html; charset=" + charsetName + "\">";
289         int pos = lcase.indexOf("<head");
290         int bodyStart;
291
292         if (pos == -1) {
293             // add <head> section
294
pos = lcase.indexOf("<html") + 6;
295             buf.append(body.substring(0, pos));
296             buf.append("<head>");
297             buf.append(meta);
298             buf.append("</head>");
299
300             bodyStart = pos;
301         } else {
302             // replace <head> section
303
pos = lcase.indexOf('>', pos) + 1;
304             buf.append(body.substring(0, pos));
305             buf.append(meta);
306
307             // TODO (@author karlpeder): If existing meta tags are to be kept, code changes are
308
// necessary
309
bodyStart = lcase.indexOf("</head");
310         }
311
312         // add rest of body until start of </body>
313
int bodyEnd = lcase.indexOf("</body");
314         buf.append(body.substring(bodyStart, bodyEnd));
315
316         // add signature if defined
317
AccountItem item = model.getAccountItem();
318         Identity identity = item.getIdentity();
319         File JavaDoc signatureFile = identity.getSignature();
320
321         if (signatureFile != null) {
322             String JavaDoc signature = getSignature(signatureFile);
323
324             if (signature != null) {
325                 buf.append("\r\n\r\n");
326
327                 // TODO: Should we take some action to ensure signature is valid
328
// html?
329
buf.append(signature);
330             }
331         }
332
333         // add the rest of the original body - and transfer back to body var.
334
buf.append(body.substring(bodyEnd));
335         body = buf.toString();
336
337         // add encoding if necessary
338
if (needQPEncoding(body)) {
339             bodyPart.getHeader().setContentTransferEncoding("quoted-printable");
340
341             // check if the charset is US-ASCII then there is something wrong
342
// -> switch to UTF-8 and write to log-file
343
if( charsetName.equalsIgnoreCase("us-ascii")){
344                 charsetName = "UTF-8";
345                 LOG.info("Charset was US-ASCII but text has 8-bit chars -> switched to UTF-8");
346             }
347         }
348
349         bodyPart.getHeader().putContentParameter("charset", charsetName);
350
351         // to allow empty messages
352
if (body.length() == 0) {
353             body = " ";
354         }
355
356         bodyPart.setBody(new CharSequenceSource(body));
357
358         return bodyPart;
359     }
360
361     /**
362      * Composes a text/plain mime part from the body contained in the composer
363      * model. This could be for a pure text message or for the text part of a
364      * multipart/alternative. <br>
365      * If the model contains a html message, tags are stripped to get plain
366      * text. <br>
367      * If a signature is defined, it is added to the body.
368      * @param appendSignature
369      *
370      * @return The composed text/plain mime part
371      */

372     private StreamableMimePart composeTextMimePart(boolean appendSignature) {
373         // Init Mime-Header with Default-Values (text/plain)
374
LocalMimePart bodyPart = new LocalMimePart(new MimeHeader("text",
375                 "plain"));
376
377         // Set Default Charset or selected
378
String JavaDoc charsetName = model.getCharset().name();
379
380
381         String JavaDoc body = model.getBodyText();
382
383         /*
384          * *20030918, karlpeder* Tags are stripped if the model contains a html
385          * message (since we are composing a plain text message here.
386          */

387         if (model.isHtml()) {
388             body = HtmlParser.htmlToText(body);
389         }
390
391         AccountItem item = model.getAccountItem();
392         Identity identity = item.getIdentity();
393         File JavaDoc signatureFile = identity.getSignature();
394
395         if (appendSignature && signatureFile != null) {
396             String JavaDoc signature = getSignature(signatureFile);
397
398             if (signature != null) {
399                 body = body + "\r\n\r\n" + signature;
400             }
401         }
402
403         if (needQPEncoding(body)) {
404             bodyPart.getHeader().setContentTransferEncoding("quoted-printable");
405             
406             // check if the charset is US-ASCII then there is something wrong
407
// -> switch to UTF-8 and write to log-file
408
if( charsetName.equalsIgnoreCase("us-ascii")){
409                 charsetName = "UTF-8";
410                 LOG.info("Charset was US-ASCII but text has 8-bit chars -> switched to UTF-8");
411             }
412         }
413         
414         // write charset to header
415
bodyPart.getHeader().putContentParameter("charset", charsetName);
416         
417         // to allow empty messages
418
if (body.length() == 0) {
419             body = " ";
420         }
421
422         bodyPart.setBody(new CharSequenceSource(body));
423
424         return bodyPart;
425     }
426
427     
428
429     public SendableMessage compose(IWorkerStatusController workerStatusController, boolean appendSignature)
430             throws Exception JavaDoc {
431         this.accountUid = model.getAccountItem().getUid();
432
433         workerStatusController.setDisplayText("Composing Message...");
434
435         MimeTreeRenderer renderer = MimeTreeRenderer.getInstance();
436         SendableMessage message = new SendableMessage();
437         SendableHeader header = initHeader();
438         MimePart root = null;
439
440         /*
441          * *20030921, karlpeder* The old code was (accidentially!?) modifying
442          * the attachment list of the model. This affects the composing when
443          * called a second time for saving the message after sending!
444          */

445
446         //List mimeParts = model.getAttachments();
447
List JavaDoc attachments = model.getAttachments();
448         List JavaDoc mimeParts = new ArrayList JavaDoc();
449         Iterator JavaDoc ite = attachments.iterator();
450
451         while (ite.hasNext()) {
452             mimeParts.add(ite.next());
453         }
454
455         // *20030919, karlpeder* Added handling of html messages
456
StreamableMimePart body;
457
458         if (model.isHtml()) {
459             // compose message body as multipart/alternative
460
XmlElement composerOptions = MailConfig.getInstance()
461                     .getComposerOptionsConfig().getRoot()
462                     .getElement("/options");
463             XmlElement html = composerOptions.getElement("html");
464
465             if (html == null) {
466                 html = composerOptions.addSubElement("html");
467             }
468
469             String JavaDoc multipart = html.getAttribute("send_as_multipart", "true");
470
471             if (multipart.equals("true")) {
472                 // send as multipart/alternative
473
body = composeMultipartAlternativeMimePart(appendSignature);
474             } else {
475                 // send as text/html
476
body = composeHtmlMimePart(appendSignature);
477             }
478         } else {
479             // compose message body as text/plain
480
body = composeTextMimePart(appendSignature);
481         }
482
483         if (body != null) {
484             mimeParts.add(0, body);
485         }
486
487         // Create Multipart/Mixed if necessary
488
if (mimeParts.size() > 1) {
489             root = new MimePart(new MimeHeader("multipart", "mixed"));
490
491             for (int i = 0; i < mimeParts.size(); i++) {
492                 root.addChild((StreamableMimePart) mimeParts.get(i));
493             }
494         } else {
495             root = (MimePart) mimeParts.get(0);
496         }
497
498         if (model.isSignMessage()) {
499             SecurityItem item = model.getAccountItem().getPGPItem();
500             String JavaDoc idStr = item.get("id");
501
502             // if the id not currently set (for example in the security panel in
503
// the account-config
504
if ((idStr == null) || (idStr.length() == 0)) {
505                 // Set id on from address
506
item.setString("id", model.getAccountItem().getIdentity()
507                         .getAddress().getMailAddress());
508             }
509
510             PGPMimePart signPart = new PGPMimePart(new MimeHeader("multipart",
511                     "signed"), item);
512
513             signPart.addChild(root);
514             root = signPart;
515         }
516
517         if (model.isEncryptMessage()) {
518             SecurityItem item = model.getAccountItem().getPGPItem();
519
520             // Set recipients from the recipients vector
521
List JavaDoc recipientList = model.getRCPTVector();
522             StringBuffer JavaDoc recipientBuf = new StringBuffer JavaDoc();
523
524             for (Iterator JavaDoc it = recipientList.iterator(); it.hasNext();) {
525                 recipientBuf.append((String JavaDoc) it.next());
526             }
527
528             item.setString("recipients", recipientBuf.toString());
529
530             PGPMimePart signPart = new PGPMimePart(new MimeHeader("multipart",
531                     "encrypted"), item);
532
533             signPart.addChild(root);
534             root = signPart;
535         }
536
537         header.setRecipients(model.getRCPTVector());
538
539         List JavaDoc headerItemList;
540
541         headerItemList = model.getToList();
542
543     
544         if ( ( headerItemList != null ) && (headerItemList.size() > 0) ){
545             Address adr = null;
546             try {
547                 adr = Address.parse((String JavaDoc) headerItemList.get(0));
548                 header.getAttributes().put("columba.to", adr);
549             } catch (ParserException e) {
550                 if (Logging.DEBUG)
551                     e.printStackTrace();
552             }
553         }
554
555         headerItemList = model.getCcList();
556
557         if ( ( headerItemList != null ) && (headerItemList.size() > 0) ) {
558             Address adr = null;
559             try {
560                 adr = Address.parse((String JavaDoc) headerItemList.get(0));
561                 header.getAttributes().put("columba.cc", adr);
562             } catch (ParserException e) {
563                 if (Logging.DEBUG)
564                     e.printStackTrace();
565             }
566
567         }
568
569         root.getHeader().getHeader().merge(header.getHeader());
570
571         InputStream JavaDoc in = renderer.renderMimePart(root);
572
573         // size
574
int size = in.available() / 1024;
575         header.getAttributes().put("columba.size", new Integer JavaDoc(size));
576
577         message.setHeader(header);
578
579         message.setAccountUid(accountUid);
580
581         //Do not access the inputstream after this line!
582
message.setSourceStream(in);
583
584         return message;
585     }
586 }
Popular Tags