/projects/ivatagroupware-0.11.3/webmail/src/java/com/ivata/groupware/business/mail/MailImpl.java
Java | 1297 lines | 629 code | 118 blank | 550 comment | 102 complexity | b397dc966b67c582b9c6d31957d24631 MD5 | raw file
1/*
2 * Copyright (c) 2001 - 2005 ivata limited.
3 * All rights reserved.
4 * -----------------------------------------------------------------------------
5 * ivata groupware may be redistributed under the GNU General Public
6 * License as published by the Free Software Foundation;
7 * version 2 of the License.
8 *
9 * These programs are free software; you can redistribute them and/or
10 * modify them under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; version 2 of the License.
12 *
13 * These programs are distributed in the hope that they will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 *
17 * See the GNU General Public License in the file LICENSE.txt for more
18 * details.
19 *
20 * If you would like a copy of the GNU General Public License write to
21 *
22 * Free Software Foundation, Inc.
23 * 59 Temple Place - Suite 330
24 * Boston, MA 02111-1307, USA.
25 *
26 *
27 * To arrange commercial support and licensing, contact ivata at
28 * http://www.ivata.com/contact.jsp
29 * -----------------------------------------------------------------------------
30 * $Log: MailImpl.java,v $
31 * Revision 1.6.2.1 2005/10/08 17:36:37 colinmacleod
32 * SettingDateFormatter now requires SecuritySession in constructor.
33 *
34 * Revision 1.6 2005/04/29 02:48:20 colinmacleod
35 * Data bugfixes.
36 * Changed primary key back to Integer.
37 *
38 * Revision 1.5 2005/04/27 15:20:09 colinmacleod
39 * Now implements Serializable.
40 *
41 * Revision 1.4 2005/04/22 10:59:12 colinmacleod
42 * Added logging when there is no mail
43 * server and reordered this file alphabetically.
44 *
45 * Revision 1.3 2005/04/10 20:10:08 colinmacleod
46 * Added new themes.
47 * Changed id type to String.
48 * Changed i tag to em and b tag to strong.
49 * Improved PicoContainerFactory with NanoContainer scripts.
50 *
51 * Revision 1.2 2005/04/09 17:20:00 colinmacleod
52 * Changed copyright text to GPL v2 explicitly.
53 *
54 * Revision 1.1.1.1 2005/03/10 17:51:15 colinmacleod
55 * Restructured ivata op around Hibernate/PicoContainer.
56 * Renamed ivata groupware.
57 *
58 * Revision 1.6 2004/11/12 15:57:23 colinmacleod
59 * Removed dependencies on SSLEXT.
60 * Moved Persistence classes to ivata masks.
61 *
62 * Revision 1.5 2004/11/03 17:15:38 colinmacleod
63 * added addUserEmailAddresses method.
64 * Improved setUesrAliases to check telecom addresses in the person.
65 *
66 * Revision 1.4 2004/09/30 15:09:33 colinmacleod
67 * Bug fixes
68 *
69 * Revision 1.3 2004/07/18 21:59:24 colinmacleod
70 * Removed Person from User - now you need to use addressbook/persistence manager to find the person (makes the app run faster.)
71 *
72 * Revision 1.2 2004/07/13 19:48:12 colinmacleod
73 * Moved project to POJOs from EJBs.
74 * Applied PicoContainer to services layer (replacing session EJBs).
75 * Applied Hibernate to persistence layer (replacing entity EJBs).
76 *
77 * Revision 1.1 2004/03/27 10:31:26 colinmacleod
78 * Split off business logic from remote facades to POJOs.
79 *
80 * Revision 1.6 2004/03/21 21:16:39 colinmacleod
81 * Shortened name to ivata op.
82 *
83 * Revision 1.5 2004/03/21 20:51:51 colinmacleod
84 * Change SecurityServer into interface.
85 * Added checking of mail server.
86 *
87 * Revision 1.4 2004/03/10 22:43:13 colinmacleod
88 * Added security server exception handling.
89 *
90 * Revision 1.3 2004/02/10 19:57:26 colinmacleod
91 * Changed email address.
92 *
93 * Revision 1.2 2004/02/01 22:07:32 colinmacleod
94 * Added full names to author tags
95 *
96 * Revision 1.1.1.1 2004/01/27 20:59:56 colinmacleod
97 * Moved ivata openportal to SourceForge..
98 *
99 * Revision 1.6 2003/12/12 13:24:34 jano
100 * fixing webmail functionality
101 *
102 * Revision 1.5 2003/11/03 11:31:06 jano
103 * commiting webmail,
104 * tryinjg to fix deploying problem
105 *
106 * Revision 1.4 2003/10/28 13:27:51 jano
107 * commiting webmail,
108 * still fixing compile and building openGroupware project
109 *
110 * Revision 1.3 2003/10/15 14:13:00 jano
111 * converting to XDoclet
112 *
113 * Revision 1.2 2003/10/15 14:11:33 colin
114 * fixing for XDoclet
115 *
116 * Revision 1.25 2003/07/15 06:43:40 peter
117 * fixed the last fix
118 *
119 * Revision 1.24 2003/07/15 06:01:59 peter
120 * fixed message text bugs and composed attachments size bug
121 *
122 * Revision 1.23 2003/07/14 15:04:22 jano
123 * peter: fixed invisible attachments problem
124 *
125 * Revision 1.22 2003/07/14 14:52:24 jano
126 * fixing bug in mailBean
127 *
128 * Revision 1.21 2003/07/11 06:31:06 peter
129 * fixed text logic in alternative multiparts
130 *
131 * Revision 1.20 2003/07/07 13:43:32 peter
132 * fixed getAttachment for cases with fileName
133 *
134 * Revision 1.19 2003/06/22 21:28:10 peter
135 * re-fixed attachment handling for multipart cases
136 *
137 * Revision 1.18 2003/06/20 18:31:03 peter
138 * added incorrectly composed mail forwards and self contained attachment like email handling
139 *
140 * Revision 1.17 2003/06/19 10:06:08 jano
141 * add check boxies in registration proces of customer
142 *
143 * Revision 1.16 2003/06/02 06:30:19 peter
144 * create reply and forward message fixed
145 *
146 * Revision 1.15 2003/05/28 05:41:21 peter
147 * added fileName as secondary attachments identifier, when contentId not present
148 *
149 * Revision 1.14 2003/05/27 17:15:12 peter
150 * getAttachment fixed, private getAttachment methnod added
151 *
152 * Revision 1.13 2003/05/15 08:21:12 peter
153 * fixed addMultipart logic - some multipart types weren't included
154 *
155 * Revision 1.12 2003/05/14 11:22:07 peter
156 * fixed bug: getDOFromJavamailMessage was called after folder closed in appendAttachmnets
157 *
158 * Revision 1.11 2003/05/13 15:24:18 peter
159 * attachment compose changes
160 *
161 * Revision 1.10 2003/05/12 16:31:13 peter
162 * attachment compose changes
163 *
164 * Revision 1.9 2003/04/01 17:58:52 colin
165 * removed boolean from InternetAddress constructor (marked as private in my JVM)
166 *
167 * Revision 1.8 2003/03/25 16:18:30 peter
168 * fixed email address validation
169 *
170 * Revision 1.7 2003/03/25 08:23:29 jano
171 * if there is no message in folder -> return null
172 * and validate the email addresses
173 *
174 * Revision 1.6 2003/03/14 10:26:46 jano
175 * adding backdoor man functionality
176 * backdoor man = briezky
177 *
178 * Revision 1.5 2003/03/03 16:57:12 colin
179 * converted localization to automatic paths
180 * added labels
181 * added mandatory fieldName attribute
182 *
183 * Revision 1.4 2003/02/28 10:23:27 peter
184 * fixed handling of plain - one part messages in getDOFromJavaMailMessage
185 *
186 * Revision 1.3 2003/02/27 17:23:09 peter
187 * Changed the return type of getAttachment to FileContentDO
188 *
189 * Revision 1.2 2003/02/25 11:53:33 colin
190 * bugfixes and minor restructuring
191 *
192 * Revision 1.1 2003/02/24 19:09:24 colin
193 * moved to business
194 *
195 * Revision 1.38 2003/02/20 20:26:15 colin
196 * improved validation by adding ValidationField and ValidationException
197 *
198 * Revision 1.37 2003/02/04 17:39:21 colin
199 * copyright notice
200 *
201 * Revision 1.36 2003/01/15 15:43:56 colin
202 * re-implemented:
203 * forwarding/replying (also to multiple messages)
204 * moving messages
205 *
206 * Revision 1.35 2002/11/20 09:21:23 peter
207 * removed duplicated function contents getDOFrom... (Jbuilder bug)
208 *
209 * Revision 1.34 2002/11/17 20:01:24 colin
210 * speed improvements in findMessagesInFolder...
211 *
212 * Revision 1.33 2002/11/12 09:12:38 colin
213 * structural changes. currently mail bean composes and reads messages but
214 * attachment & thread handling not active
215 *
216 * Revision 1.32 2002/10/25 08:31:44 peter
217 * mailFolderSent setting name changed to emailFolderSent
218 *
219 * Revision 1.31 2002/10/23 12:44:37 jano
220 * using new method for get System userName
221 *
222 * Revision 1.30 2002/10/23 09:18:59 jano
223 * there is a new method for generating SystemUserName
224 *
225 * Revision 1.29 2002/10/18 09:18:48 colin
226 * check users to make sure they are enabled before sending them mail
227 *
228 * Revision 1.28 2002/10/14 11:15:46 peter
229 * fixed a bug in (precomposed) send method, the cc fields work now
230 *
231 * Revision 1.27 2002/10/11 10:05:38 jano
232 * add PREFIX to user name for difren site
233 *
234 * Revision 1.26 2002/10/10 14:03:57 peter
235 * changes due to demo version
236 *
237 * Revision 1.25 2002/10/01 05:59:47 peter
238 * modifications in (precomposed) send method
239 *
240 * Revision 1.24 2002/09/17 07:26:24 peter
241 * working version
242 *
243 * Revision 1.23 2002/09/16 16:26:40 peter
244 * the attachments stuff works....
245 *
246 * Revision 1.22 2002/09/13 13:59:17 peter
247 * appendMessages and setDO methods tuned...
248 * it still doesn't work properly
249 *
250 * Revision 1.21 2002/09/12 15:55:25 peter
251 * tuned createMessage and setDO
252 *
253 * Revision 1.20 2002/09/12 07:26:19 colin
254 * added vacation message and user alias methods
255 *
256 * Revision 1.19 2002/09/11 15:57:48 peter
257 * finished createMessage and setDO, debugging needed yet
258 *
259 * Revision 1.18 2002/09/11 11:33:12 peter
260 * moveMessage works, works on createMessage and setDO
261 *
262 * Revision 1.17 2002/09/10 15:38:51 peter
263 * MailBean: works on methods
264 *
265 * Revision 1.16 2002/09/10 14:18:51 peter
266 * MailBean: works on methods
267 *
268 * Revision 1.15 2002/09/10 08:20:16 peter
269 * MailBean: added moveMessage method
270 *
271 * Revision 1.14 2002/09/09 16:07:37 peter
272 * added and modified methods in mail/MailBean
273 *
274 * Revision 1.13 2002/09/09 08:27:24 colin
275 * changed mail bean from stateful to stateless
276 * added new MailSession class
277 *
278 * Revision 1.12 2002/08/30 09:50:31 colin
279 * changed canUser... methods to just can...
280 *
281 * Revision 1.11 2002/08/29 12:23:06 peter
282 * mail display works...
283 *
284 * Revision 1.10 2002/08/27 15:26:25 peter
285 * worked on getDO, should be finished
286 *
287 * Revision 1.9 2002/08/26 15:30:14 peter
288 * MessageDO integration, not finished yet
289 *
290 * Revision 1.8 2002/08/26 11:15:47 peter
291 * added getDo and the basic methods work
292 *
293 * Revision 1.7 2002/08/23 08:09:37 peter
294 * design for MailBean methods, display so far
295 *
296 * Revision 1.6 2002/08/16 12:35:22 peter
297 * fiixed a minor bug in getMessage method
298 *
299 * Revision 1.5 2002/08/16 11:59:00 peter
300 * new mail accessing methods
301 *
302 * Revision 1.4 2002/08/11 11:37:50 colin
303 * added routines to handle server activation and passivisation
304 *
305 * Revision 1.3 2002/07/26 13:08:06 colin
306 * first version with mail server support
307 *
308 * Revision 1.2 2002/07/15 13:29:27 jano
309 * added CreateException
310 *
311 * Revision 1.1 2002/07/15 07:51:04 colin
312 * added new Mail EJB and local interface to settings
313 * -----------------------------------------------------------------------------
314 */
315package com.ivata.groupware.business.mail;
316
317import java.io.ByteArrayOutputStream;
318import java.io.File;
319import java.io.IOException;
320import java.io.InputStream;
321import java.io.Serializable;
322import java.text.MessageFormat;
323import java.util.Arrays;
324import java.util.Calendar;
325import java.util.Collection;
326import java.util.Date;
327import java.util.GregorianCalendar;
328import java.util.Iterator;
329import java.util.List;
330import java.util.Set;
331import java.util.TreeMap;
332import java.util.Vector;
333
334import javax.activation.DataHandler;
335import javax.activation.DataSource;
336import javax.activation.FileDataSource;
337import javax.mail.Address;
338import javax.mail.AuthenticationFailedException;
339import javax.mail.Flags;
340import javax.mail.Folder;
341import javax.mail.FolderNotFoundException;
342import javax.mail.Message;
343import javax.mail.MessagingException;
344import javax.mail.NoSuchProviderException;
345import javax.mail.Part;
346import javax.mail.Session;
347import javax.mail.Store;
348import javax.mail.Transport;
349import javax.mail.internet.AddressException;
350import javax.mail.internet.InternetAddress;
351import javax.mail.internet.MimeBodyPart;
352import javax.mail.internet.MimeMessage;
353import javax.mail.internet.MimeMultipart;
354import javax.mail.internet.MimePart;
355
356import org.apache.log4j.Logger;
357
358import com.ivata.groupware.admin.security.server.SecurityServer;
359import com.ivata.groupware.admin.security.server.SecurityServerException;
360import com.ivata.groupware.admin.security.server.SecuritySession;
361import com.ivata.groupware.admin.security.user.UserDO;
362import com.ivata.groupware.admin.setting.Settings;
363import com.ivata.groupware.admin.setting.SettingsDataTypeException;
364import com.ivata.groupware.business.addressbook.AddressBook;
365import com.ivata.groupware.business.addressbook.person.PersonDO;
366import com.ivata.groupware.business.addressbook.telecomaddress.TelecomAddressConstants;
367import com.ivata.groupware.business.addressbook.telecomaddress.TelecomAddressDO;
368import com.ivata.groupware.business.drive.file.FileContentDO;
369import com.ivata.groupware.business.drive.file.FileDO;
370import com.ivata.groupware.business.mail.message.MessageDO;
371import com.ivata.groupware.business.mail.message.MessageNotFoundException;
372import com.ivata.groupware.business.mail.server.MailServer;
373import com.ivata.groupware.business.mail.server.NoMailServerException;
374import com.ivata.groupware.business.mail.session.MailSession;
375import com.ivata.groupware.util.SettingDateFormatter;
376import com.ivata.groupware.web.format.EmailAddressFormatter;
377import com.ivata.groupware.web.format.SanitizerFormat;
378import com.ivata.mask.Mask;
379import com.ivata.mask.MaskFactory;
380import com.ivata.mask.util.SerializedByteArray;
381import com.ivata.mask.util.StringHandling;
382import com.ivata.mask.util.SystemException;
383import com.ivata.mask.validation.ValidationError;
384import com.ivata.mask.validation.ValidationErrors;
385import com.ivata.mask.web.format.CharacterEntityFormat;
386import com.ivata.mask.web.format.FormatConstants;
387import com.ivata.mask.web.format.HTMLFormatter;
388import com.ivata.mask.web.format.LineBreakFormat;
389import com.ivata.mask.web.tag.webgui.list.ListColumnComparator;
390
391
392/**
393 * <p>This session bean provides an interface to the mail system. Every mail
394 * operation for retrieving deleting and sending messages takes place in this
395 * class.</p>
396 *
397 * @since 2002-07-12
398 * @author Colin MacLeod
399 * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
400 * @author Peter Illes
401 * @version $Revision: 1.6.2.1 $
402 */
403public class MailImpl implements Mail, Serializable {
404 /**
405 * <p>Used to return both the HTML & plain text parts of a message in
406 * <code>createThreadMessage</code>.</p>
407 */
408 private class MessageTextParts {
409 public MimeBodyPart HTMLPart = null;
410 public MimeBodyPart textPart = null;
411 }
412
413 /**
414 * Logger for this class.
415 */
416 private static Logger logger = Logger.getLogger(MailImpl.class);
417 private AddressBook addressBook;
418 private SettingDateFormatter dateFormatter = null;
419 private MailServer mailServer;
420 MaskFactory maskFactory;
421 /**
422 * <p>
423 * Settings implementation. Used to retrieve the email address host.
424 * </p>
425 */
426 private Settings settings;
427
428
429 /**
430 * <p>
431 * Initialize the mail implementation.
432 * </p>
433 *
434 * @param securityServer A valid security server for the current site. If
435 * this is not an instance of {@link com.ivata.groupware.business.mail.server.MailServer}
436 * the mail implementation will not be usable.
437 * @param persistenceManager This is used to save/access data from the
438 * persistence store.
439 * @param addressBook This is used to read contacts email details.
440 * @param settings Contains user defined settings and preferences.
441 * @param dateFormatter Used to format mail dates and times.
442 * @param idDispenser
443 */
444 public MailImpl(SecurityServer securityServer,
445 AddressBook addressBook,
446 Settings settings,
447 MaskFactory maskFactory) {
448 assert (securityServer != null);
449 if (securityServer instanceof MailServer) {
450 this.mailServer = (MailServer) securityServer;
451 } else {
452 logger.warn("Security server class ("
453 + securityServer.getClass().getName()
454 + ") is not a mail server class.");
455 }
456 this.settings = settings;
457 this.addressBook = addressBook;
458 this.maskFactory = maskFactory;
459 }
460 private void checkDateFormatter(SecuritySession securitySession) {
461 if (dateFormatter == null) {
462 dateFormatter = new SettingDateFormatter(securitySession,
463 settings);
464 }
465 }
466 /**
467 * <p>Add a composed message to the drafts folder for later sending.</p>
468 *
469 * @param mailSession valid mail session to which the user should already be
470 * logged in.
471 * @param messageDO data object containing full details of the
472 * message to be added to the drafts.
473 * @return new <code>MessageDO</code> with the <code>id</code> set to the
474 * current value in the mail system.
475 */
476 public MessageDO addMessageToDraftsFolder(final MailSession mailSession,
477 final MessageDO messageDO) throws SystemException {
478 checkDateFormatter(mailSession);
479
480 Store store = mailServer.connectStore(mailSession);
481 try {
482 Session javaMailSession;
483 try {
484 javaMailSession = mailSession.getJavaMailSession();
485 } catch (java.security.NoSuchProviderException e1) {
486 throw new SystemException(e1);
487 }
488
489 // get the drafts folder in case we want to copy over an older mail
490 Folder draftsFolder = openDraftsFolder(store, mailSession);
491
492 MimeMessage newMessage = setDOToJavaMailMessage(javaMailSession,
493 draftsFolder, messageDO);
494
495 newMessage.setSentDate(Calendar.getInstance().getTime());
496
497 // append the new message to the drafts folder
498 Message[] messages = { newMessage };
499
500 draftsFolder.appendMessages(messages);
501
502 // note the new id
503 messageDO.setMessageID(((MimeMessage) draftsFolder.getMessage(
504 draftsFolder.getMessageCount())).getMessageID());
505
506 // only now can we delete/expunge the old mail from the drafts folder
507 draftsFolder.expunge();
508 draftsFolder.close(true);
509 } catch (MessagingException e1) {
510 throw new SystemException(e1);
511 } finally {
512 try {
513 store.close();
514 } catch (MessagingException e) {
515 logger.error("Messaging exception on closing the store", e);
516 }
517 }
518
519 return messageDO;
520 }
521
522 /**
523 * <p>Recursive routine used for building up all attachments in the
524 * <code>MessageDO<code> provided.</p>
525 *
526 * @param messagePart the multipart part or message to process.
527 * @param messageDO the data object to add results to.
528 * @throws GroupwareException if there is a <code>MailMessagingException</code> or
529 * <code>IOException</code>.
530 */
531 private void addMultiPart(final Part messagePart,
532 final MessageDO messageDO)
533 throws SystemException {
534 String outputText = "";
535 MimeMultipart content;
536 MimeBodyPart subPart;
537
538 List messageTextParts = new Vector();
539
540 try {
541 content = (MimeMultipart) messagePart.getContent();
542
543 //go through all the subParts
544 for (int i = 0; i < content.getCount(); i++) {
545 subPart = (MimeBodyPart) content.getBodyPart(i);
546
547 // when multipart/alternative and no text found in parent call,
548 // store the text of the parts to select the best one after the loop
549 if (messagePart.isMimeType("multipart/alternative") &&
550 subPart.isMimeType("text/*") &&
551 StringHandling.isNullOrEmpty(messageDO.getText())) {
552 messageTextParts.add(subPart);
553 } else if (messagePart.isMimeType("multipart/*")) {
554 // other multipart types
555 if (StringHandling.isNullOrEmpty(messageDO.getText()) &&
556 (subPart.getDisposition() == null) &&
557 (subPart.getFileName() == null)) {
558 if (subPart.isMimeType("text/*")) {
559 messageTextParts.add(subPart);
560 } else if (subPart.isMimeType("multipart/*")) {
561 addMultiPart((Part) subPart, messageDO);
562 } else {
563 addPart(subPart, messageDO);
564 }
565 } else {
566 if (subPart.isMimeType("multipart/*")) {
567 addMultiPart((Part) subPart, messageDO);
568 } else {
569 addPart(subPart, messageDO);
570 }
571 }
572 }
573 }
574
575 // looking for best message text
576 if (!messageTextParts.isEmpty()) {
577 String HTML = null;
578 String text = null;
579
580 // let's choose the best text
581 for (Iterator i = messageTextParts.iterator(); i.hasNext();) {
582 subPart = (MimeBodyPart) i.next();
583
584 if (subPart.isMimeType("text/HTML")) {
585 HTML = (String) subPart.getContent();
586 } else if (subPart.isMimeType("text/plain")) {
587 text = (String) subPart.getContent();
588 }
589 // TODO: we could use text/enriched too
590 }
591
592 if (HTML != null) {
593 messageDO.setText(HTML);
594 messageDO.setFormat(FormatConstants.FORMAT_HTML);
595 } else if (text != null) {
596 messageDO.setText(text);
597 messageDO.setFormat(FormatConstants.FORMAT_TEXT);
598 }
599 }
600 } catch (MessagingException e) {
601 throw new SystemException(e);
602 } catch (java.io.IOException e) {
603 throw new SystemException(e);
604 }
605 }
606
607 /**
608 * <p>Add a part of a multi-part message to the attachments of the message
609 * data object.</p>
610 *
611 * @param part the message part to process.
612 * @param messageDO the data object to add results to.
613 * @throws GroupwareException if there is a <code>MessagingException</code> or an
614 * <code>IOException</code>.
615 */
616 private void addPart(final MimePart part,
617 final MessageDO messageDO)
618 throws SystemException {
619 FileDO attachment = new FileDO();
620
621 try {
622 attachment.setMimeType(part.getContentType());
623 attachment.setSize(new Integer(part.getSize()));
624
625 String contentId = part.getContentID();
626 String name = part.getFileName();
627
628 // return with empty hands if no identifier, the attachment will be
629 // impossible to locate
630 if ((contentId == null) && (name == null)) {
631 return;
632 }
633
634 // prefer contentId as identifier and name as the display info
635 attachment.setName((contentId != null) ? contentId : name);
636 attachment.setComment((name != null) ? name : contentId);
637 } catch (MessagingException e) {
638 throw new SystemException(e);
639 }
640
641 messageDO.getAttachments().add(attachment);
642 }
643
644 /**
645 * <p>Internal helper to add a message to the sent mail folder. This should
646 * be called <em><u>after</u></em> sending the mail.</p>
647 */
648 private void addToSentFolder(final MailSession mailSession,
649 final MimeMessage message)
650 throws SystemException {
651 Folder sentFolder;
652
653 Store store = mailServer.connectStore(mailSession);
654 try {
655 String sentFolderName = settings.getStringSetting(
656 mailSession,
657 "emailFolderSent",
658 mailSession.getUser());
659 sentFolder = mailServer.getFolder(mailSession, store, sentFolderName);
660
661 if (!sentFolder.exists()) {
662 try {
663 if (!sentFolder.create(Folder.HOLDS_MESSAGES)) {
664 throw new SystemException(
665 "There was no sent folder for you on this server, "
666 + "and ivata mail could not create one.<br>Please "
667 + "contact your administrator.");
668 }
669 } catch (MessagingException e1) {
670 throw new SystemException(e1);
671 }
672 }
673
674 Message[] messages = { message };
675
676 try {
677 sentFolder.appendMessages(messages);
678 } catch (MessagingException eAppend) {
679 throw new SystemException(
680 "There was an error adding your message to the sent "
681 + "messages folder: " +
682 eAppend.getMessage(),
683 eAppend);
684 }
685 } catch (MessagingException eNoSent) {
686 throw new SystemException("Sent folder not available in store. " +
687 eNoSent.getMessage(),
688 eNoSent);
689 } catch (SettingsDataTypeException e) {
690 throw new SystemException(e);
691 } finally {
692 try {
693 store.close();
694 } catch (MessagingException e) {
695 logger.error("Messaging exception on closing the store", e);
696 }
697 }
698 }
699
700 /**
701 * <p>
702 * Add appropriate user addresses given a list of user aliases.
703 * </p>
704 *
705 * @param securitySession valid security session.
706 * @param userName name of the user who owns teh aliases.
707 * @param userAliases a <code>Collection</code> of <code>String</code>
708 * instances containing the local part of the different email aliases
709 * this user has. If the user has no aliaes, an empty collection should
710 * be provided.
711 * @param telecomAddresess a <code>Collection</code> containing all the
712 * user's existing email addresses, as <code>TelecomAddressDO</code>
713 * instances.
714 */
715 public void addUserAliasEmailAddresses(final SecuritySession securitySession,
716 final String userName,
717 final Collection userAliases,
718 final Collection telecomAddresses,
719 final String emailAddressHost)
720 throws SystemException {
721 checkDateFormatter(securitySession);
722 Iterator telecomAddressIterator = telecomAddresses.iterator();
723 List currentAddresses = new Vector();
724 while (telecomAddressIterator.hasNext()) {
725 TelecomAddressDO thisTelecomAddress = (TelecomAddressDO)
726 telecomAddressIterator.next();
727 if (!StringHandling.isNullOrEmpty(thisTelecomAddress.getAddress())
728 && (thisTelecomAddress.getType() == TelecomAddressConstants.TYPE_EMAIL)) {
729 currentAddresses.add(thisTelecomAddress.getAddress());
730 }
731 }
732
733 // if the person has no email address, give him/her one
734 // check there is at least one alias - the following routine will
735 // do the rest
736 // we make it conditional because you might not always want to have
737 // user@host as your email address - this way, you can specify a
738 // different one
739 if ((currentAddresses.size() == 0)
740 && (userAliases.size() == 0)) {
741 userAliases.add(userName);
742 }
743
744 // go thro' all aliases and create email addreses from them
745 Iterator aliasIterator = userAliases.iterator();
746 while(aliasIterator.hasNext()) {
747 String alias = (String) aliasIterator.next();
748 String aliasAddress = alias + "@" + emailAddressHost;
749 // if it is already there, move on...
750 if (currentAddresses.contains(aliasAddress)) {
751 continue;
752 }
753 TelecomAddressDO newAddress = new TelecomAddressDO();
754 newAddress.setAddress(aliasAddress);
755 newAddress.setType(TelecomAddressConstants.TYPE_EMAIL);
756 newAddress.setNumber(telecomAddresses.size());
757 telecomAddresses.add(newAddress);
758 }
759 }
760
761 /**
762 * <p>Append attachments to a message located in the drafts folder.</p>
763 *
764 * @param mailSession valid mail session to which the user should already be
765 * logged in.
766 * @param id the unique identifier of the message to which we want to append
767 * attachments.
768 * @param attachments <code>List</code> of <code>String</code>s -
769 * filenames of files waiting in upload directory.
770 * @return <code>null</code> when the operation failed, otherwise the new
771 * message id.
772 * @exception MessageNotFoundException if the folder doesn't exist, or there
773 * is no matching mail in this folder.
774 *
775 * @ejb.interface-method
776 * view-type = "remote"
777 */
778 public MessageDO appendAttachments(final MailSession mailSession,
779 final String id,
780 final List attachments) throws SystemException {
781 checkDateFormatter(mailSession);
782 String newId = null;
783 UserDO user = mailSession.getUser();
784 Store store = mailServer.connectStore(mailSession);
785
786 try {
787 String siteHome = settings.getStringSetting(mailSession,
788 "siteHome", user);
789 String uploadDirectory = siteHome + "/users/" +
790 mailSession.authenticator.getPasswordAuthentication()
791 .getUserName() + "/upload/files/";
792
793 Session javaMailSession;
794
795 try {
796 javaMailSession = mailSession.getJavaMailSession();
797 } catch (SecurityServerException e) {
798 throw new SystemException(e);
799 } catch (java.security.NoSuchProviderException e) {
800 throw new SystemException(e);
801 }
802
803 Folder draftsFolder = openDraftsFolder(store, mailSession);
804 MimeMessage oldMessage = findJavaMailMessageByFolderMessageId(draftsFolder,
805 id);
806 int i;
807 MimeBodyPart newPart;
808 MimeMultipart newMessageContent = new MimeMultipart();
809 MimeMultipart oldMessageContent;
810 MimeMessage newMessage = copyJavaMailMessage(javaMailSession,
811 oldMessage);
812
813 // when the message is already multipart/mixed, no probs...
814 if (oldMessage.isMimeType("multipart/mixed")) {
815 oldMessageContent = (MimeMultipart) oldMessage.getContent();
816
817 for (i = 0; i < oldMessageContent.getCount(); i++) {
818 newPart = (MimeBodyPart) oldMessageContent.getBodyPart(i);
819 newMessageContent.addBodyPart(newPart);
820 }
821 } else {
822 // create the first part from the old message, attachments will be appended
823 newPart = new MimeBodyPart();
824 newPart.setContent(oldMessage.getContent(),
825 oldMessage.getContentType());
826 newPart.setHeader("Content-Type", oldMessage.getContentType());
827 newMessageContent.addBodyPart(newPart);
828 }
829
830 // add the attachments, passed as fileNames of files in upload dir
831 for (Iterator attachmentsIterator = attachments.iterator();
832 attachmentsIterator.hasNext();) {
833 File attachment = new File(uploadDirectory,
834 (String) attachmentsIterator.next());
835
836 // process the file in upload directory
837 if (attachment.canRead()) {
838 newPart = new MimeBodyPart();
839 newPart.setFileName(attachment.getName());
840 newPart.setDisposition(Part.ATTACHMENT);
841
842 DataSource dataSource = new FileDataSource(attachment);
843 newPart.setDataHandler(new DataHandler(dataSource));
844 newPart.setHeader("Content-Type",
845 dataSource.getContentType());
846 newPart.setHeader("Content-Transfer-Encoding", "base64");
847 newMessageContent.addBodyPart(newPart);
848 }
849 }
850
851 newMessage.setContent(newMessageContent);
852 newMessage.setHeader("Content-Type",
853 newMessageContent.getContentType());
854
855 // first append the new message
856 Message[] messages = { newMessage };
857
858 draftsFolder.appendMessages(messages);
859
860 // note the new id
861 newId = ((MimeMessage) draftsFolder.getMessage(draftsFolder.getMessageCount())).getMessageID();
862
863 // only now is it safe to delete the old message
864 oldMessage.setFlag(Flags.Flag.DELETED, true);
865 draftsFolder.expunge();
866
867 // now it's safe to delete the files in upload dir, they're in the new multipart
868 for (Iterator attachmentsIterator = attachments.iterator();
869 attachmentsIterator.hasNext();) {
870 File attachment = new File(uploadDirectory,
871 (String) attachmentsIterator.next());
872
873 if (attachment.canWrite()) {
874 attachment.delete();
875 }
876 }
877
878 // MessageDO returnDO = getDOFromJavaMailMessage(newMessage, true);
879 MessageDO returnDO = getDOFromJavaMailMessage(
880 findJavaMailMessageByFolderMessageId(
881 draftsFolder, newId), true);
882 draftsFolder.close(true);
883 return returnDO;
884 } catch (MessagingException em) {
885 throw new SystemException(em);
886 } catch (IOException eio) {
887 throw new SystemException(eio);
888 } finally {
889 try {
890 store.close();
891 } catch (MessagingException e) {
892 logger.error("Messaging exception on closing the store", e);
893 }
894 }
895 }
896
897 /**
898 * <p>
899 * Check we have a valid mail server.
900 * </p>
901 *
902 * @throws NoMailServerException if there is no mail server.
903 */
904 private void checkMailServer() throws SystemException {
905 if (mailServer == null) {
906 logger.warn("No mail server found.");
907 throw new NoMailServerException();
908 }
909 }
910
911 /**
912 * <p>Helper method. Converts recipients from a collection of
913 * <code>PersonDO</code>, <code>UserDO</code> or <code>String<code>
914 * instances into an array of email addresses.</p>
915 * @param securitySession TODO
916 * @param addresses a <code>Collection</code> containing all of the email
917 * addresses to convert. These can either be as <code>String<code>
918 * instances or <code>PersonDO<code> instances, where the default
919 * email address for each person is taken.
920 *
921 * @return array or <code>InternetAddress</code> instances for each of the
922 * input parameters.
923 */
924 private InternetAddress[] convertAddresses(final SecuritySession securitySession,
925 final Collection addresses)
926 throws SystemException {
927 InternetAddress[] returnAddresses = new InternetAddress[addresses.size()];
928
929 // prerequisites check we got given something to convert
930 if (addresses == null) {
931 return returnAddresses;
932 }
933
934 int index = 0;
935
936 for (Iterator i = addresses.iterator(); i.hasNext();) {
937 Object item = i.next();
938 String addressString = null;
939
940 if (PersonDO.class.isInstance(item)) {
941 PersonDO person = (PersonDO) item;
942
943 addressString = person.getEmailAddress();
944 } else if (UserDO.class.isInstance(item)) {
945 UserDO user = (UserDO) item;
946 PersonDO person = addressBook.findPersonByUserName(securitySession,
947 user.getName());
948
949 // only set the address for users who are not disabled
950 if (user.isEnabled()) {
951 addressString = person.getEmailAddress();
952 }
953 } else {
954 if (!String.class.isInstance(item)) {
955 throw new SystemException("Cannot convert item of class '" +
956 item.getClass() + "' into an email address.");
957 }
958
959 addressString = (String) item;
960 }
961
962 // ignore empty addresses
963 if (!StringHandling.isNullOrEmpty(addressString)) {
964 try {
965 returnAddresses[index++] = new InternetAddress(addressString);
966 } catch (AddressException eAddress) {
967 throw new SystemException(
968 "ERROR in MailBean: cannot convert internet address '"
969 + addressString
970 + "': "
971 + eAddress.getMessage(),
972 eAddress);
973 }
974 }
975 }
976
977 return returnAddresses;
978 }
979
980 /**
981 * <p>Copy the fields of an old <code>MimeMessage</code> to a new one.</p>
982 *
983 * <p><strong>Note:</strong> this method does not copy the content. Both the text
984 * and the message attachments (if any) must be set individually.</p>
985 *
986 * @param javaMailSession valid <em>JavaMail</em> session to which the user
987 * should already be logged in.
988 * @param message a valid message filled out with values to copy.
989 * @return a valid <em>JavaMail</em> message ready with <code>recipients</code>,
990 * <code>from</code> and <code>subject</code> fields matching
991 * <code>message</code>.
992 */
993 private MimeMessage copyJavaMailMessage(final Session javaMailSession,
994 final MimeMessage message)
995 throws SystemException {
996 MimeMessage newMessage = new MimeMessage(javaMailSession);
997
998 try {
999 newMessage.setRecipients(Message.RecipientType.TO,
1000 message.getRecipients(Message.RecipientType.TO));
1001 newMessage.setRecipients(Message.RecipientType.CC,
1002 message.getRecipients(Message.RecipientType.CC));
1003 newMessage.setRecipients(Message.RecipientType.BCC,
1004 message.getRecipients(Message.RecipientType.BCC));
1005 newMessage.addFrom(message.getFrom());
1006 newMessage.setSubject(message.getSubject());
1007 } catch (MessagingException e) {
1008 throw new SystemException(e);
1009 }
1010
1011 return newMessage;
1012 }
1013
1014 /**
1015 * <p>Convert a <em>JavaMail</em> message to an <em>ivata groupware</em> dependent
1016 * value object.</p>
1017 *
1018 * @param message a valid <em>JavaMail</em> message to be converted.
1019 * @param includeContent <code>true</code> if the <code>text</code> and
1020 * attachments of the message should also be set, otherwise
1021 * <code>false</code>.
1022 * @return message data object with the values filled out to match
1023 * the <em>JavaMail</em> object.
1024 */
1025 private MessageDO createDOFromJavaMailMessage(final MimeMessage message,
1026 final boolean includeContent)
1027 throws SystemException {
1028 // right - we got here, so that means we have a message
1029 MessageDO messageDO = new MessageDO();
1030 try {
1031 //setting the fields of the MessageDO:
1032 if (message.getFolder() != null) {
1033 messageDO.setFolderName(message.getFolder().getName());
1034 }
1035
1036 if (message.getReceivedDate() != null) {
1037 GregorianCalendar receivedDate = new GregorianCalendar();
1038
1039 receivedDate.setTime(message.getReceivedDate());
1040 messageDO.setReceived(receivedDate);
1041 }
1042
1043 if (message.getRecipients(Message.RecipientType.TO) != null) {
1044 messageDO.setRecipients(Arrays.asList(toStringArray(
1045 message.getRecipients(Message.RecipientType.TO))));
1046 }
1047
1048 if (message.getRecipients(Message.RecipientType.CC) != null) {
1049 messageDO.setRecipientsCC(Arrays.asList(toStringArray(
1050 message.getRecipients(Message.RecipientType.CC))));
1051 }
1052
1053 if (message.getRecipients(Message.RecipientType.BCC) != null) {
1054 messageDO.setRecipientsBCC(Arrays.asList(toStringArray(
1055 message.getRecipients(Message.RecipientType.BCC))));
1056 }
1057
1058 if (message.getFrom() != null) {
1059 messageDO.setSenders(Arrays.asList(toStringArray(
1060 message.getFrom())));
1061 }
1062
1063 if (message.getSentDate() != null) {
1064 GregorianCalendar sentDate = new GregorianCalendar();
1065
1066 sentDate.setTime(message.getSentDate());
1067 messageDO.setSent(sentDate);
1068 }
1069
1070 messageDO.setSize(new Integer(message.getSize()));
1071 messageDO.setSubject(message.getSubject());
1072
1073 // message content handling - not always done for efficiency in lists
1074 if (includeContent) {
1075 Integer format;
1076 String text;
1077
1078 // create new, empty List for our attachments
1079 messageDO.setAttachments(new Vector());
1080
1081 // if it is a multipart message (has attachments), pass control to
1082 // recursive routine to go thro' them all
1083 if (message.isMimeType("multipart/*")) {
1084 addMultiPart(message, messageDO);
1085
1086 // here are types with textual content
1087 } else if (message.isMimeType("text/*") ||
1088 message.isMimeType("message/*")) {
1089 // if it is not multipart, we're just left with text or HTML
1090 if (message.isMimeType("text/HTML")) {
1091 // simple message with HTML content
1092 messageDO.setFormat(FormatConstants.FORMAT_HTML);
1093 } else {
1094 // anything else with simple content should have text content
1095 messageDO.setFormat(FormatConstants.FORMAT_TEXT);
1096 }
1097
1098 messageDO.setText((String) message.getContent());
1099
1100 // other (not correct?) types, as self-contained attachments...
1101 } else {
1102 messageDO.setFormat(FormatConstants.FORMAT_TEXT);
1103 messageDO.setText("");
1104 addPart(message, messageDO);
1105 }
1106 }
1107 messageDO.setMessageID(message.getMessageID());
1108 } catch (MessagingException e) {
1109 throw new SystemException(e);
1110 } catch (IOException e) {
1111 throw new SystemException(e);
1112 }
1113
1114 return messageDO;
1115 }
1116
1117 /**
1118 * <p>Create a new mail folder.</p>
1119 *
1120 * @param mailSession valid mail session to which the user should already be
1121 * logged in.
1122 * @param folderName the full path name of the folder to create.
1123 *
1124 * @ejb.interface-method
1125 * view-type = "remote"
1126 */
1127 public void createFolder(final MailSession mailSession,
1128 final String folderName)
1129 throws SystemException {
1130 assert (mailSession != null);
1131 checkDateFormatter(mailSession);
1132
1133 Session javaMailSession;
1134
1135 try {
1136 javaMailSession = mailSession.getJavaMailSession();
1137 } catch (AuthenticationFailedException e) {
1138 throw new SystemException(
1139 "User is no longer authorized to use this server: " +
1140 e.getMessage(),
1141 e);
1142 } catch (MessagingException e) {
1143 throw new SystemException(e);
1144 } catch (SecurityServerException e) {
1145 throw new SystemException(e);
1146 } catch (java.security.NoSuchProviderException e) {
1147 throw new SystemException(e);
1148 }
1149
1150 Store store = mailServer.connectStore(mailSession);
1151 try {
1152 if (folderName == null) {
1153 throw new SystemException(
1154 "ERROR in MailBean.createFolder: folderName is null");
1155 }
1156
1157 Folder folder = mailServer.getFolder(mailSession, store, folderName);
1158
1159 if (!folder.create(Folder.HOLDS_MESSAGES)) {
1160 throw new SystemException(
1161 "ERROR in MailBean.createFolder: could not create folder '" +
1162 folderName + "'");
1163 }
1164 } catch (MessagingException e) {
1165 throw new SystemException(e);
1166 } finally {
1167 try {
1168 store.close();
1169 } catch (MessagingException e) {
1170 logger.error("Messaging exception on closing the store", e);
1171 }
1172 }
1173 }
1174
1175
1176 /**
1177 * <p>Helper method for <code>createThreadMethod</code>.</p>
1178 *
1179 * <p>Create a new message in the drafts folder from an existing one,
1180 * resulting in a forwarded message.</p>
1181 *
1182 * @param mailSession valid mail session to which the user should already be
1183 * logged in.
1184 * @param folderName the name of the folder to copy existing messages from.
1185 * @param messageIds the unique identifier of the messages to be extended.
1186 * Can be <code>null</code> if a new message is requeested. When
1187 * forwarding, multiple address identifiers may be specified otherwise
1188 * (if editing a draft message or replying) only one message identifier
1189 * should be set in the list.
1190 * @param thread set to one of the constants in {@link MailConstants
1191 * MailConstants}.
1192 * @return populated message data object matching the required
1193 * message, and with the <code>id</code> set to the message in the
1194 * drafts folder.
1195 */
1196 private MimeMessage createForwardedMessage(final MailSession mailSession,
1197 final Folder folder,
1198 final List messageIds)
1199 throws SystemException {
1200 checkDateFormatter(mailSession);
1201 try {
1202 Session javaMailSession;
1203 try {
1204 javaMailSession = mailSession.getJavaMailSession();
1205 } catch (java.security.NoSuchProviderException e1) {
1206 throw new SystemException(e1);
1207 }
1208 UserDO user = mailSession.getUser();
1209
1210 // if this is HTML, we'll need to store multipart data
1211 MessageTextParts messageTextParts = null;
1212
1213 // first go thro' all the messages and see if there are _any_ which
1214 // are multipart
1215 boolean isMultipart = false;
1216 int format = FormatConstants.FORMAT_TEXT;
1217
1218 for (Iterator i = messageIds.iterator(); i.hasNext();) {
1219 String id = (String) i.next();
1220 MimeMessage oldMessage = findJavaMailMessageByFolderMessageId(folder,
1221 id);
1222
1223 // is this multipart?
1224 if (oldMessage.isMimeType("multipart/*")) {
1225 isMultipart = true;
1226
1227 // try to find an HTML subpart
1228 messageTextParts = getMultiPartText(oldMessage);
1229
1230 if (messageTextParts.HTMLPart != null) {
1231 format = FormatConstants.FORMAT_HTML;
1232
1233 // no need to check any further...
1234 break;
1235 }
1236 }
1237 }
1238
1239 // text header/prefix depends on the format
1240 String messageHeader;
1241
1242 if (format == FormatConstants.FORMAT_HTML) {
1243 messageHeader = settings.getStringSetting(mailSession,
1244 "emailHeaderForwardHTML",
1245 user);
1246 } else {
1247 messageHeader = settings.getStringSetting(mailSession,
1248 "emailHeaderForwardText",
1249 user);
1250 }
1251
1252 MimeMessage newMessage = new MimeMessage(javaMailSession);
1253 StringBuffer subject = new StringBuffer();
1254 String subjectPrefix = settings.getStringSetting(mailSession,
1255 "emailSubjectForwardPrefix",
1256 user);
1257 String subjectSeperator = settings.getStringSetting(mailSession,
1258 "emailSubjectForwardSeperator",
1259 user);
1260
1261 subject.append(subjectPrefix);
1262
1263 StringBuffer messageText = new StringBuffer();
1264
1265 // we'll format the reply text, if it is text & this is HTML
1266 CharacterEntityFormat characterEntities = new CharacterEntityFormat();
1267
1268 // go thro' all of the old ids again, this time to add the content
1269 int index = 0;
1270
1271 for (Iterator i = messageIds.iterator(); i.hasNext(); ++index) {
1272 String id = (String) i.next();
1273 MimeMessage oldMessage = findJavaMailMessageByFolderMessageId(folder,
1274 id);
1275
1276 // prepend Re: or Fwd: unless the previous subject already starts like this
1277 String oldSubject = StringHandling.getNotNull(oldMessage.getSubject(),
1278 getNullString());
1279
1280 // if there is a fwd: prefix check this message doesn't start with
1281 // that
1282 if ((subjectPrefix != null) &&
1283 (oldSubject.startsWith(subjectPrefix))) {
1284 oldSubject = oldSubject.substring(subjectPrefix.length());
1285 }
1286
1287 // if there is more than one forwarded message, append separator
1288 // between the subjects
1289 if ((index > 0) && !oldSubject.equals("")) {
1290 subject.append(subjectSeperator);
1291 }
1292
1293 subject.append(oldSubject);
1294
1295 // locate the multipart in the new message, for multiparts
1296 String oldMessageText = null;
1297 int oldFormat = FormatCon