source: trunk/src/org/expeditee/agents/mail/MailSession.java@ 1102

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

File size: 20.4 KB
Line 
1/**
2 * MailSession.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.agents.mail;
20
21import java.io.BufferedReader;
22import java.io.InputStream;
23import java.io.InputStreamReader;
24import java.util.Collection;
25import java.util.Date;
26import java.util.LinkedList;
27import java.util.Properties;
28
29import javax.mail.Address;
30import javax.mail.Authenticator;
31import javax.mail.Folder;
32import javax.mail.Message;
33import javax.mail.MessageRemovedException;
34import javax.mail.MessagingException;
35import javax.mail.Multipart;
36import javax.mail.Part;
37import javax.mail.PasswordAuthentication;
38import javax.mail.Session;
39import javax.mail.Store;
40import javax.mail.Transport;
41import javax.mail.Flags.Flag;
42import javax.mail.Message.RecipientType;
43import javax.mail.event.MessageCountAdapter;
44import javax.mail.event.MessageCountEvent;
45import javax.mail.internet.InternetAddress;
46import javax.mail.internet.MimeMessage;
47
48import org.expeditee.core.Colour;
49import org.expeditee.core.Point;
50import org.expeditee.gio.DragAndDropManager;
51import org.expeditee.gui.AttributeValuePair;
52import org.expeditee.gui.DisplayController;
53import org.expeditee.gui.Frame;
54import org.expeditee.gui.FrameCreator;
55import org.expeditee.gui.MessageBay;
56import org.expeditee.items.Text;
57
58public class MailSession {
59 public static final String UNREAD_MESSAGE = " unread message";
60
61 public static boolean _autoConnect = false;
62
63 private static MailSession _theMailSession = null;
64
65 private Session _session;
66
67 private Transport _transport;
68
69 private Store _store;
70
71 private Folder _folder;
72
73 private String _address;
74
75 private String _username;
76
77 private String _password;
78
79 private String _mailServer;
80
81 private String _serverType;
82
83 private Boolean _bConnecting;
84
85 private MailSession(Frame settingsFrame) {
86 _bConnecting = false;
87
88 Properties props = System.getProperties();
89
90 _username = null;
91 _password = "";
92 _mailServer = null;
93 _serverType = null;
94
95 // Set the settings
96 for (Text item : settingsFrame.getBodyTextItems(false)) {
97 if (item.getText().toLowerCase().trim().equals("autoconnect")) {
98 _autoConnect = true;
99 continue;
100 }
101 AttributeValuePair avp = new AttributeValuePair(item.getText());
102 if (!avp.hasPair())
103 continue;
104 String attributeFullCase = avp.getAttribute();
105 String attribute = attributeFullCase.toLowerCase();
106
107 if (attribute.equals("user")) {
108 _username = avp.getValue();
109 props.setProperty("mail.user", _username);
110 } else if (attribute.equals("password")) {
111 _password = avp.getValue();
112 props.setProperty("mail.password", _password);
113 } else if (attribute.equals("address")) {
114 _address = avp.getValue();
115 } else if (attribute.equals("smtpserver")) {
116 props.setProperty("mail.transport.protocol", "smtp");
117 props.setProperty("mail.host", avp.getValue());
118 props.setProperty("mail.smtp.starttls.enable", "true");
119 props.setProperty("mail.smtp.host", avp.getValue());
120 props.setProperty("mail.smtp.auth", "true");
121 } else if (attribute.equals("popserver")) {
122 _mailServer = avp.getValue();
123 _serverType = "pop3s";
124 props.setProperty("mail.pop3.host", _mailServer);
125 } else if (attribute.equals("imapserver")) {
126 _mailServer = avp.getValue();
127 _serverType = "imaps";
128 props.setProperty("mail.imap.host", _mailServer);
129 }
130 }
131
132 // Create the authenticator
133 Authenticator auth = null;
134 if (_username != null) {
135 auth = new SMTPAuthenticator(_username, _password);
136 }
137// java.security.Security
138// .addProvider(new com.sun.net.ssl.internal.ssl.Provider());
139
140 // -- Attaching to default Session, or we could start a new one --
141 _session = Session.getDefaultInstance(props, auth);
142 try {
143 // Set up the mail receiver
144 _store = _session.getStore(_serverType);
145
146 // Connect the mail sender
147 _transport = _session.getTransport();
148 if (_autoConnect) {
149 connectThreaded();
150 }
151 } catch (Exception e) {
152 MessageBay.errorMessage("Error in ExpMail setup");
153 }
154 }
155
156 /**
157 * Attempts to connect the mail transporter to the mail server.
158 *
159 */
160 public static void connect() {
161 if (_theMailSession._bConnecting) {
162 MessageBay.errorMessage("Already connecting to mail server");
163 return;
164 } else if (_theMailSession != null) {
165 _theMailSession.connectThreaded();
166 }
167 }
168
169 private void connectThreaded() {
170 Thread t = new ConnectThread(this);
171 t.start();
172 }
173
174 public synchronized void connectServers() {
175 try {
176 if (!_transport.isConnected()) {
177 // MessageBay.displayMessage("Connecting to SMTP server...");
178 _bConnecting = true;
179 _transport.connect();
180 // MessageBay.displayMessage("SMTP server connected",
181 // Color.green);
182 } else {
183 MessageBay.warningMessage("SMTP server already connected");
184 }
185 } catch (MessagingException e) {
186 MessageBay.errorMessage("Error connecting to SMTP server");
187 }
188
189 if (_mailServer != null && !_store.isConnected()) {
190 try {
191 // Text message = MessageBay.displayMessage("Connecting to "
192 // + _mailServer + "...");
193 _store.connect(_mailServer, _username, _password);
194
195 // -- Try to get hold of the default folder --
196 _folder = _store.getDefaultFolder();
197 if (_folder == null)
198 throw new Exception("No default folder");
199 // -- ...and its INBOX --
200 _folder = _folder.getFolder("INBOX");
201 if (_folder == null)
202 throw new Exception("No INBOX");
203 // -- Open the folder for read only --
204 _folder.open(Folder.READ_WRITE);
205 _folder.addMessageCountListener(new MessageCountAdapter() {
206 @Override
207 public void messagesAdded(MessageCountEvent e) {
208 try {
209 MessageBay.displayMessage("New mail message!",
210 null, Colour.GREEN, true, "getMailByID "
211 + _folder.getMessageCount());
212 /*
213 * TODO use messageID incase mail gets deleted
214 * externally
215 */
216 } catch (MessagingException e1) {
217 e1.printStackTrace();
218 }
219 displayUnreadMailCount();
220 }
221 });
222
223 new Thread() {
224 public void run() {
225 for (;;) {
226 try {
227 Thread.sleep(5000);
228 /*
229 * sleep for freq milliseconds. This is to force
230 * the IMAP server to send us EXISTS
231 * notifications
232 */
233 // TODO: Is synchronisation needed?
234 _folder.getMessageCount();
235 _folder.exists();
236 // _folder.getUnreadMessageCount();
237 } catch (Exception e) {
238 e.printStackTrace();
239 MessageBay
240 .errorMessage("Mail connection unavailable");
241 finalise();
242 break;
243 }
244 }
245 }
246 }.start();
247
248 MessageBay.displayMessage("Mail connection complete",
249 Colour.GREEN);
250
251 displayUnreadMailCount();
252
253 } catch (Exception e) {
254 // e.printStackTrace();
255 MessageBay.errorMessage("Error connecting to " + _mailServer);
256 }
257 }
258 _bConnecting = false;
259 }
260
261 public void displayUnreadMailCount() {
262 int unreadCount = getUnreadCount();
263 Text text = new Text(-1, unreadCount + UNREAD_MESSAGE
264 + (unreadCount == 1 ? "" : "s"), Colour.BLUE, null);
265 if (unreadCount > 0)
266 text.addAction("getUnreadMail " + unreadCount);
267 MessageBay.displayMessage(text);
268 }
269
270 public static boolean sendTextMessage(String to, String cc, String bcc,
271 String subject, String body, Object attachments) {
272
273 if (_theMailSession == null) {
274 MessageBay.errorMessage("Add mail settings to profile frame");
275 return false;
276 }
277
278 if (_theMailSession._bConnecting) {
279 MessageBay.errorMessage("Busy connecting to mail server...");
280 return false;
281 }
282
283 return _theMailSession
284 .sendText(to, cc, bcc, subject, body, attachments);
285 }
286
287 private synchronized boolean sendText(String to, String cc, String bcc,
288 String subject, String body, Object attachments) {
289 if (!_transport.isConnected()) {
290 MessageBay
291 .warningMessage("Not connected to server, attempting to reconnect...");
292 try {
293 _bConnecting = true;
294 _transport.connect();
295 _bConnecting = false;
296 } catch (Exception e) {
297 MessageBay.errorMessage("Could not connect to mail server");
298 _bConnecting = false;
299 return false;
300 }
301 }
302
303 if (to == null) {
304 MessageBay.errorMessage("Add tag @to:<sendToEmailAddress>");
305 return false;
306 }
307
308 try {
309 // -- Create a new message --
310 Message msg = new MimeMessage(_session);
311
312 // -- Set the FROM and TO fields --
313 msg.setFrom(new InternetAddress(_address));
314 msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(
315 to, false));
316
317 // -- We could include CC recipients too --
318 if (cc != null) {
319 msg.setRecipients(Message.RecipientType.CC, InternetAddress
320 .parse(cc, false));
321 }
322
323 if (bcc != null) {
324 msg.setRecipients(Message.RecipientType.BCC, InternetAddress
325 .parse(bcc, false));
326 }
327
328 // -- Set the subject and body text --
329 msg.setSubject(subject);
330 msg.setContent(body.toString(), "text/plain");
331
332 // -- Set some other header information --
333 msg.setHeader("ExpMail", "Expeditee");
334 msg.setSentDate(new Date());
335
336 Transport.send(msg, msg.getRecipients(Message.RecipientType.TO));
337 } catch (Exception e) {
338 MessageBay.errorMessage("Error sending mail: " + e.getMessage());
339 return false;
340 }
341 MessageBay.displayMessage("Message sent OK.");
342 return true;
343 }
344
345 public static void init(Frame settingsFrame) {
346
347 if (settingsFrame == null)
348 return;
349
350 if (_theMailSession == null)
351 _theMailSession = new MailSession(settingsFrame);
352 }
353
354 private class SMTPAuthenticator extends javax.mail.Authenticator {
355 private String _username;
356
357 private String _password;
358
359 public SMTPAuthenticator(String username, String password) {
360 _username = username;
361 _password = password;
362 }
363
364 @Override
365 public PasswordAuthentication getPasswordAuthentication() {
366 return new PasswordAuthentication(_username, _password);
367 }
368 }
369
370 public static MailSession getInstance() {
371 return _theMailSession;
372 }
373
374 /**
375 * Closes the mail folders.
376 *
377 * @return true if the folders needed to be closed.
378 */
379 public synchronized boolean finalise() {
380 boolean result = false;
381 try {
382 if (_transport != null && _transport.isConnected()) {
383 _transport.close();
384 result = true;
385 }
386
387 if (_folder != null && _folder.isOpen()) {
388 _folder.close(false);
389 result = true;
390 }
391
392 if (_store != null && _store.isConnected()) {
393 _store.close();
394 result = true;
395 }
396 } catch (Exception e) {
397
398 }
399 return result;
400 }
401
402 public int getUnreadCount() {
403 try {
404 return _folder.getUnreadMessageCount();
405 } catch (MessagingException e) {
406 // TODO Auto-generated catch block
407 e.printStackTrace();
408 }
409 return 0;
410 }
411
412 /**
413 * Gets mail and puts a list of mail items with links to the messages
414 * content. TODO: Put reply and forward button on the frame...
415 *
416 * @param flag
417 * @param isPresent
418 * @param frame
419 * @param point
420 * @return
421 */
422 public String getMailString(Flag flag, Boolean isPresent) {
423 StringBuffer sb = new StringBuffer();
424 // -- Get the message wrappers and process them --
425 Message[] msgs;
426 try {
427 msgs = _folder.getMessages();
428 for (int msgNum = 0; msgNum < msgs.length; msgNum++) {
429
430 if (flag == null
431 || msgs[msgNum].getFlags().contains(flag) == isPresent) {
432 if (sb.length() > 0) {
433 sb.append('\n').append('\n').append(
434 "-----------------------------").append('\n')
435 .append('\n');
436 }
437 // Only get messages that have not been read
438 sb.append(getTextMessage(msgs[msgNum]));
439 }
440 }
441 } catch (MessagingException e) {
442 e.printStackTrace();
443 }
444 return sb.toString();
445 }
446
447 /**
448 * Gets mail and puts a list of mail items with links to the messages
449 * content. TODO: Put reply and forward button on the frame...
450 *
451 * @param flag
452 * @param isPresent
453 * @param frame
454 * @param point
455 * @return
456 */
457 public Collection<Text> getMail(Flag flag, Boolean isPresent, Frame frame,
458 Point point, int numberOfMessages) {
459 if (_folder == null)
460 return null;
461
462 Collection<Text> mailItems = new LinkedList<Text>();
463 // -- Get the message wrappers and process them --
464 Message[] msgs;
465 try {
466 msgs = _folder.getMessages();
467
468 // msgs[0].get
469
470 int messagesRead = 0;
471
472 for (int msgNum = msgs.length - 1; messagesRead < numberOfMessages
473 && msgNum >= 0; msgNum--) {
474 if (flag == null
475 || msgs[msgNum].getFlags().contains(flag) == isPresent) {
476 Text newItem = readMessage(msgs[msgNum], msgNum + 1, frame,
477 point);
478 // TODO: May want to reverse the order of mail messages
479 if (newItem != null) {
480 mailItems.add(newItem);
481 point.y += newItem.getBoundsHeight();
482 messagesRead++;
483 } else {
484 newItem = null;
485 }
486 }
487
488 }
489 } catch (MessagingException e) {
490 e.printStackTrace();
491 }
492
493 return mailItems;
494 }
495
496 public Text getMail(Frame frame, Point point, int msgNum) {
497 if (_folder == null)
498 return null;
499
500 // -- Get the message wrappers and process them --
501 try {
502 Message[] msgs = _folder.getMessages();
503 return readMessage(msgs[msgNum], msgNum + 1, frame, point);
504 } catch (ArrayIndexOutOfBoundsException ae) {
505 /*
506 * Just return null... error message will be displayed in the
507 * calling method
508 */
509 } catch (Exception e) {
510 e.printStackTrace();
511 }
512
513 return null;
514 }
515
516 private Text readMessage(final Message message, final int messageNo,
517 final Frame frame, final Point point) {
518
519 final Text source = DragAndDropManager.importString("Reading message " + messageNo + "...", point);
520
521 new Thread() {
522 public void run() {
523 try {
524 String subject = message.getSubject();
525 source.setText("[" + messageNo + "] " + subject);
526 // Create a frameCreator
527 final FrameCreator frames = new FrameCreator(frame
528 .getFramesetName(), frame.getPath(), subject,
529 false, false);
530
531 frames.addText("@date: " + message.getSentDate(), null,
532 null, null, false);
533
534 // Get the header information
535 String from = ((InternetAddress) message.getFrom()[0])
536 .toString();
537 Text fromAddressItem = frames.addText("@from: " + from,
538 null, null, null, false);
539
540 addRecipients(message, frames, _address, RecipientType.TO,
541 "@to: ");
542 addRecipients(message, frames, null, RecipientType.CC,
543 "@cc: ");
544
545 // Read reply to addresses
546 Text reply = addAddresses(message, frames, from, message
547 .getReplyTo(), "@replyTo: ");
548 /*
549 * If the only person to reply to is the person who sent the
550 * mail add a tag that just says reply
551 */
552 if (reply == null) {
553 reply = frames.addText("@reply", null, null, null,
554 false);
555 reply.setPosition(10 + fromAddressItem.getX()
556 + fromAddressItem.getBoundsWidth(),
557 fromAddressItem.getY());
558 }
559 reply.addAction("reply");
560 // frames.addSpace(15);
561
562 // -- Get the message part (i.e. the message itself) --
563 Part messagePart = message;
564 Object content = messagePart.getContent();
565 // -- or its first body part if it is a multipart
566 // message --
567 if (content instanceof Multipart) {
568 messagePart = ((Multipart) content).getBodyPart(0);
569 // System.out.println("[ Multipart Message ]");
570 }
571 // -- Get the content type --
572 String contentType = messagePart.getContentType()
573 .toLowerCase();
574 // -- If the content is plain text, we can print it --
575 // System.out.println("CONTENT:" + contentType);
576 if (contentType.startsWith("text/plain")
577 || contentType.startsWith("text/html")) {
578 InputStream is = messagePart.getInputStream();
579 BufferedReader reader = new BufferedReader(
580 new InputStreamReader(is));
581 String thisLine = reader.readLine();
582 StringBuffer nextText = new StringBuffer();
583 while (thisLine != null) {
584 // A blank line is a signal to start a new text item
585 if (thisLine.trim().equals("")) {
586 addTextItem(frames, nextText.toString());
587 nextText = new StringBuffer();
588 } else {
589 nextText.append(thisLine).append('\n');
590 }
591 thisLine = reader.readLine();
592 }
593 addTextItem(frames, nextText.toString());
594 }
595 message.setFlag(Flag.SEEN, true);
596
597 frames.save();
598 source.setLink(frames.getName());
599 DisplayController.requestRefresh(true);
600 } catch (MessageRemovedException mre) {
601 source.setText("Message removed from inbox");
602 } catch (MessagingException e) {
603 String message = e.getMessage();
604 if (message == null) {
605 e.printStackTrace();
606 MessageBay.errorMessage("GetMail error!");
607 } else {
608 MessageBay.errorMessage("GetMail error: " + message);
609 }
610 } catch (Exception e) {
611 MessageBay.errorMessage("Error reading mail: "
612 + e.getMessage());
613 e.printStackTrace();
614 }
615 }
616
617 /**
618 * @param frames
619 * @param nextText
620 */
621 private void addTextItem(final FrameCreator frames, String nextText) {
622 nextText = nextText.trim();
623 if (nextText.length() == 0)
624 return;
625 // Remove the last char if its a newline
626 if (nextText.charAt(nextText.length() - 1) == '\n')
627 nextText = nextText.substring(0, nextText.length() - 1);
628 // TODO: Make the space a setting in frame creator
629 frames.addSpace(10);
630 frames.addText(nextText, null, null, null, false);
631 }
632 }.start();
633 return source;
634 }
635
636 /**
637 * "getTextMessage()" method to print a message.
638 */
639 public String getTextMessage(Message message) {
640 StringBuffer sb = new StringBuffer();
641
642 try {
643 // Get the header information
644 String from = ((InternetAddress) message.getFrom()[0])
645 .getPersonal();
646 if (from == null)
647 from = ((InternetAddress) message.getFrom()[0]).getAddress();
648 sb.append("FROM: " + from).append('\n');
649 String subject = message.getSubject();
650 sb.append("SUBJECT: " + subject).append('\n').append('\n');
651 // -- Get the message part (i.e. the message itself) --
652 Part messagePart = message;
653 Object content = messagePart.getContent();
654 // -- or its first body part if it is a multipart message --
655 if (content instanceof Multipart) {
656 messagePart = ((Multipart) content).getBodyPart(0);
657 System.out.println("[ Multipart Message ]");
658 }
659 // -- Get the content type --
660 String contentType = messagePart.getContentType();
661 // -- If the content is plain text, we can print it --
662 // System.out.println("CONTENT:" + contentType);
663 if (contentType.startsWith("text/plain")
664 || contentType.startsWith("text/html")) {
665 InputStream is = messagePart.getInputStream();
666 BufferedReader reader = new BufferedReader(
667 new InputStreamReader(is));
668 String thisLine = reader.readLine();
669 while (thisLine != null) {
670 sb.append(thisLine).append('\n');
671 thisLine = reader.readLine();
672 }
673 }
674 message.setFlag(Flag.SEEN, true);
675 } catch (Exception ex) {
676 ex.printStackTrace();
677 }
678 sb.deleteCharAt(sb.length() - 1);
679 return sb.toString();
680 }
681
682 public Folder getFolder() {
683 return _folder;
684 }
685
686 /**
687 * @param message
688 * @param frames
689 * @param type
690 * @throws MessagingException
691 */
692 private Text addAddresses(final Message message, final FrameCreator frames,
693 final String excludeAddress, final Address[] addresses,
694 String typeTag) throws MessagingException {
695 if (addresses == null)
696 return null;
697
698 StringBuffer sb = new StringBuffer();
699 boolean foundOtherRecipients = false;
700 for (Address addy : addresses) {
701 // Only show the to flag if this message was sent to
702 // other people
703 if (excludeAddress == null
704 || !addy.toString().toLowerCase().contains(
705 excludeAddress.toLowerCase())) {
706 foundOtherRecipients = true;
707 }
708 if (sb.length() > 0) {
709 sb.append(", ");
710 }
711 sb.append(addy.toString());
712 }
713 Text reply = null;
714 if (foundOtherRecipients) {
715 reply = frames.addText(typeTag + sb.toString(), null, null, null,
716 false);
717 }
718 return reply;
719 }
720
721 private Text addRecipients(final Message message,
722 final FrameCreator frames, String excludeAddress,
723 RecipientType type, String typeTag) throws MessagingException {
724 // Read and display all the recipients of the message
725 Address[] toRecipients = message.getRecipients(type);
726 return addAddresses(message, frames, excludeAddress, toRecipients,
727 typeTag);
728 }
729}
Note: See TracBrowser for help on using the repository browser.