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

Last change on this file since 919 was 919, checked in by jts21, 10 years ago

Added license headers to all files, added full GPL3 license file, moved license header generator script to dev/bin/scripts

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.awt.Color;
22import java.awt.Point;
23import java.io.BufferedReader;
24import java.io.InputStream;
25import java.io.InputStreamReader;
26import java.util.Collection;
27import java.util.Date;
28import java.util.LinkedList;
29import java.util.Properties;
30
31import javax.mail.Address;
32import javax.mail.Authenticator;
33import javax.mail.Folder;
34import javax.mail.Message;
35import javax.mail.MessageRemovedException;
36import javax.mail.MessagingException;
37import javax.mail.Multipart;
38import javax.mail.Part;
39import javax.mail.PasswordAuthentication;
40import javax.mail.Session;
41import javax.mail.Store;
42import javax.mail.Transport;
43import javax.mail.Flags.Flag;
44import javax.mail.Message.RecipientType;
45import javax.mail.event.MessageCountAdapter;
46import javax.mail.event.MessageCountEvent;
47import javax.mail.internet.InternetAddress;
48import javax.mail.internet.MimeMessage;
49
50import org.expeditee.gui.AttributeValuePair;
51import org.expeditee.gui.Frame;
52import org.expeditee.gui.FrameCreator;
53import org.expeditee.gui.FrameGraphics;
54import org.expeditee.gui.MessageBay;
55import org.expeditee.importer.FrameDNDTransferHandler;
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, Color.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 Color.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"), Color.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 = FrameDNDTransferHandler.importString(
520 "Reading message " + messageNo + "...", point);
521
522 new Thread() {
523 public void run() {
524 try {
525 String subject = message.getSubject();
526 source.setText("[" + messageNo + "] " + subject);
527 // Create a frameCreator
528 final FrameCreator frames = new FrameCreator(frame
529 .getFramesetName(), frame.getPath(), subject,
530 false, false);
531
532 frames.addText("@date: " + message.getSentDate(), null,
533 null, null, false);
534
535 // Get the header information
536 String from = ((InternetAddress) message.getFrom()[0])
537 .toString();
538 Text fromAddressItem = frames.addText("@from: " + from,
539 null, null, null, false);
540
541 addRecipients(message, frames, _address, RecipientType.TO,
542 "@to: ");
543 addRecipients(message, frames, null, RecipientType.CC,
544 "@cc: ");
545
546 // Read reply to addresses
547 Text reply = addAddresses(message, frames, from, message
548 .getReplyTo(), "@replyTo: ");
549 /*
550 * If the only person to reply to is the person who sent the
551 * mail add a tag that just says reply
552 */
553 if (reply == null) {
554 reply = frames.addText("@reply", null, null, null,
555 false);
556 reply.setPosition(10 + fromAddressItem.getX()
557 + fromAddressItem.getBoundsWidth(),
558 fromAddressItem.getY());
559 }
560 reply.addAction("reply");
561 // frames.addSpace(15);
562
563 // -- Get the message part (i.e. the message itself) --
564 Part messagePart = message;
565 Object content = messagePart.getContent();
566 // -- or its first body part if it is a multipart
567 // message --
568 if (content instanceof Multipart) {
569 messagePart = ((Multipart) content).getBodyPart(0);
570 // System.out.println("[ Multipart Message ]");
571 }
572 // -- Get the content type --
573 String contentType = messagePart.getContentType()
574 .toLowerCase();
575 // -- If the content is plain text, we can print it --
576 // System.out.println("CONTENT:" + contentType);
577 if (contentType.startsWith("text/plain")
578 || contentType.startsWith("text/html")) {
579 InputStream is = messagePart.getInputStream();
580 BufferedReader reader = new BufferedReader(
581 new InputStreamReader(is));
582 String thisLine = reader.readLine();
583 StringBuffer nextText = new StringBuffer();
584 while (thisLine != null) {
585 // A blank line is a signal to start a new text item
586 if (thisLine.trim().equals("")) {
587 addTextItem(frames, nextText.toString());
588 nextText = new StringBuffer();
589 } else {
590 nextText.append(thisLine).append('\n');
591 }
592 thisLine = reader.readLine();
593 }
594 addTextItem(frames, nextText.toString());
595 }
596 message.setFlag(Flag.SEEN, true);
597
598 frames.save();
599 source.setLink(frames.getName());
600 FrameGraphics.requestRefresh(true);
601 } catch (MessageRemovedException mre) {
602 source.setText("Message removed from inbox");
603 } catch (MessagingException e) {
604 String message = e.getMessage();
605 if (message == null) {
606 e.printStackTrace();
607 MessageBay.errorMessage("GetMail error!");
608 } else {
609 MessageBay.errorMessage("GetMail error: " + message);
610 }
611 } catch (Exception e) {
612 MessageBay.errorMessage("Error reading mail: "
613 + e.getMessage());
614 e.printStackTrace();
615 }
616 }
617
618 /**
619 * @param frames
620 * @param nextText
621 */
622 private void addTextItem(final FrameCreator frames, String nextText) {
623 nextText = nextText.trim();
624 if (nextText.length() == 0)
625 return;
626 // Remove the last char if its a newline
627 if (nextText.charAt(nextText.length() - 1) == '\n')
628 nextText = nextText.substring(0, nextText.length() - 1);
629 // TODO: Make the space a setting in frame creator
630 frames.addSpace(10);
631 frames.addText(nextText, null, null, null, false);
632 }
633 }.start();
634 return source;
635 }
636
637 /**
638 * "getTextMessage()" method to print a message.
639 */
640 public String getTextMessage(Message message) {
641 StringBuffer sb = new StringBuffer();
642
643 try {
644 // Get the header information
645 String from = ((InternetAddress) message.getFrom()[0])
646 .getPersonal();
647 if (from == null)
648 from = ((InternetAddress) message.getFrom()[0]).getAddress();
649 sb.append("FROM: " + from).append('\n');
650 String subject = message.getSubject();
651 sb.append("SUBJECT: " + subject).append('\n').append('\n');
652 // -- Get the message part (i.e. the message itself) --
653 Part messagePart = message;
654 Object content = messagePart.getContent();
655 // -- or its first body part if it is a multipart message --
656 if (content instanceof Multipart) {
657 messagePart = ((Multipart) content).getBodyPart(0);
658 System.out.println("[ Multipart Message ]");
659 }
660 // -- Get the content type --
661 String contentType = messagePart.getContentType();
662 // -- If the content is plain text, we can print it --
663 // System.out.println("CONTENT:" + contentType);
664 if (contentType.startsWith("text/plain")
665 || contentType.startsWith("text/html")) {
666 InputStream is = messagePart.getInputStream();
667 BufferedReader reader = new BufferedReader(
668 new InputStreamReader(is));
669 String thisLine = reader.readLine();
670 while (thisLine != null) {
671 sb.append(thisLine).append('\n');
672 thisLine = reader.readLine();
673 }
674 }
675 message.setFlag(Flag.SEEN, true);
676 } catch (Exception ex) {
677 ex.printStackTrace();
678 }
679 sb.deleteCharAt(sb.length() - 1);
680 return sb.toString();
681 }
682
683 public Folder getFolder() {
684 return _folder;
685 }
686
687 /**
688 * @param message
689 * @param frames
690 * @param type
691 * @throws MessagingException
692 */
693 private Text addAddresses(final Message message, final FrameCreator frames,
694 final String excludeAddress, final Address[] addresses,
695 String typeTag) throws MessagingException {
696 if (addresses == null)
697 return null;
698
699 StringBuffer sb = new StringBuffer();
700 boolean foundOtherRecipients = false;
701 for (Address addy : addresses) {
702 // Only show the to flag if this message was sent to
703 // other people
704 if (excludeAddress == null
705 || !addy.toString().toLowerCase().contains(
706 excludeAddress.toLowerCase())) {
707 foundOtherRecipients = true;
708 }
709 if (sb.length() > 0) {
710 sb.append(", ");
711 }
712 sb.append(addy.toString());
713 }
714 Text reply = null;
715 if (foundOtherRecipients) {
716 reply = frames.addText(typeTag + sb.toString(), null, null, null,
717 false);
718 }
719 return reply;
720 }
721
722 private Text addRecipients(final Message message,
723 final FrameCreator frames, String excludeAddress,
724 RecipientType type, String typeTag) throws MessagingException {
725 // Read and display all the recipients of the message
726 Address[] toRecipients = message.getRecipients(type);
727 return addAddresses(message, frames, excludeAddress, toRecipients,
728 typeTag);
729 }
730}
Note: See TracBrowser for help on using the repository browser.