/** * MailSession.java * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.expeditee.agents.mail; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.Properties; import javax.mail.Address; import javax.mail.Authenticator; import javax.mail.Folder; import javax.mail.Message; import javax.mail.MessageRemovedException; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Store; import javax.mail.Transport; import javax.mail.Flags.Flag; import javax.mail.Message.RecipientType; import javax.mail.event.MessageCountAdapter; import javax.mail.event.MessageCountEvent; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import org.expeditee.core.Colour; import org.expeditee.core.Point; import org.expeditee.gio.DragAndDropManager; import org.expeditee.gui.AttributeValuePair; import org.expeditee.gui.DisplayController; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameCreator; import org.expeditee.gui.MessageBay; import org.expeditee.items.Text; public class MailSession { public static final String UNREAD_MESSAGE = " unread message"; public static boolean _autoConnect = false; private static MailSession _theMailSession = null; private Session _session; private Transport _transport; private Store _store; private Folder _folder; private String _address; private String _username; private String _password; private String _mailServer; private String _serverType; private Boolean _bConnecting; private MailSession(Frame settingsFrame) { _bConnecting = false; Properties props = System.getProperties(); _username = null; _password = ""; _mailServer = null; _serverType = null; // Set the settings for (Text item : settingsFrame.getBodyTextItems(false)) { if (item.getText().toLowerCase().trim().equals("autoconnect")) { _autoConnect = true; continue; } AttributeValuePair avp = new AttributeValuePair(item.getText()); if (!avp.hasPair()) continue; String attributeFullCase = avp.getAttribute(); String attribute = attributeFullCase.toLowerCase(); if (attribute.equals("user")) { _username = avp.getValue(); props.setProperty("mail.user", _username); } else if (attribute.equals("password")) { _password = avp.getValue(); props.setProperty("mail.password", _password); } else if (attribute.equals("address")) { _address = avp.getValue(); } else if (attribute.equals("smtpserver")) { props.setProperty("mail.transport.protocol", "smtp"); props.setProperty("mail.host", avp.getValue()); props.setProperty("mail.smtp.starttls.enable", "true"); props.setProperty("mail.smtp.host", avp.getValue()); props.setProperty("mail.smtp.auth", "true"); } else if (attribute.equals("popserver")) { _mailServer = avp.getValue(); _serverType = "pop3s"; props.setProperty("mail.pop3.host", _mailServer); } else if (attribute.equals("imapserver")) { _mailServer = avp.getValue(); _serverType = "imaps"; props.setProperty("mail.imap.host", _mailServer); } } // Create the authenticator Authenticator auth = null; if (_username != null) { auth = new SMTPAuthenticator(_username, _password); } // java.security.Security // .addProvider(new com.sun.net.ssl.internal.ssl.Provider()); // -- Attaching to default Session, or we could start a new one -- _session = Session.getDefaultInstance(props, auth); try { // Set up the mail receiver _store = _session.getStore(_serverType); // Connect the mail sender _transport = _session.getTransport(); if (_autoConnect) { connectThreaded(); } } catch (Exception e) { MessageBay.errorMessage("Error in ExpMail setup"); } } /** * Attempts to connect the mail transporter to the mail server. * */ public static void connect() { if (_theMailSession._bConnecting) { MessageBay.errorMessage("Already connecting to mail server"); return; } else if (_theMailSession != null) { _theMailSession.connectThreaded(); } } private void connectThreaded() { Thread t = new ConnectThread(this); t.start(); } public synchronized void connectServers() { try { if (!_transport.isConnected()) { // MessageBay.displayMessage("Connecting to SMTP server..."); _bConnecting = true; _transport.connect(); // MessageBay.displayMessage("SMTP server connected", // Color.green); } else { MessageBay.warningMessage("SMTP server already connected"); } } catch (MessagingException e) { MessageBay.errorMessage("Error connecting to SMTP server"); } if (_mailServer != null && !_store.isConnected()) { try { // Text message = MessageBay.displayMessage("Connecting to " // + _mailServer + "..."); _store.connect(_mailServer, _username, _password); // -- Try to get hold of the default folder -- _folder = _store.getDefaultFolder(); if (_folder == null) throw new Exception("No default folder"); // -- ...and its INBOX -- _folder = _folder.getFolder("INBOX"); if (_folder == null) throw new Exception("No INBOX"); // -- Open the folder for read only -- _folder.open(Folder.READ_WRITE); _folder.addMessageCountListener(new MessageCountAdapter() { @Override public void messagesAdded(MessageCountEvent e) { try { MessageBay.displayMessage("New mail message!", null, Colour.GREEN, true, "getMailByID " + _folder.getMessageCount()); /* * TODO use messageID incase mail gets deleted * externally */ } catch (MessagingException e1) { e1.printStackTrace(); } displayUnreadMailCount(); } }); new Thread() { public void run() { for (;;) { try { Thread.sleep(5000); /* * sleep for freq milliseconds. This is to force * the IMAP server to send us EXISTS * notifications */ // TODO: Is synchronisation needed? _folder.getMessageCount(); _folder.exists(); // _folder.getUnreadMessageCount(); } catch (Exception e) { e.printStackTrace(); MessageBay .errorMessage("Mail connection unavailable"); finalise(); break; } } } }.start(); MessageBay.displayMessage("Mail connection complete", Colour.GREEN); displayUnreadMailCount(); } catch (Exception e) { // e.printStackTrace(); MessageBay.errorMessage("Error connecting to " + _mailServer); } } _bConnecting = false; } public void displayUnreadMailCount() { int unreadCount = getUnreadCount(); Text text = new Text(-1, unreadCount + UNREAD_MESSAGE + (unreadCount == 1 ? "" : "s"), Colour.BLUE, null); if (unreadCount > 0) text.addAction("getUnreadMail " + unreadCount); MessageBay.displayMessage(text); } public static boolean sendTextMessage(String to, String cc, String bcc, String subject, String body, Object attachments) { if (_theMailSession == null) { MessageBay.errorMessage("Add mail settings to profile frame"); return false; } if (_theMailSession._bConnecting) { MessageBay.errorMessage("Busy connecting to mail server..."); return false; } return _theMailSession .sendText(to, cc, bcc, subject, body, attachments); } private synchronized boolean sendText(String to, String cc, String bcc, String subject, String body, Object attachments) { if (!_transport.isConnected()) { MessageBay .warningMessage("Not connected to server, attempting to reconnect..."); try { _bConnecting = true; _transport.connect(); _bConnecting = false; } catch (Exception e) { MessageBay.errorMessage("Could not connect to mail server"); _bConnecting = false; return false; } } if (to == null) { MessageBay.errorMessage("Add tag @to:"); return false; } try { // -- Create a new message -- Message msg = new MimeMessage(_session); // -- Set the FROM and TO fields -- msg.setFrom(new InternetAddress(_address)); msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse( to, false)); // -- We could include CC recipients too -- if (cc != null) { msg.setRecipients(Message.RecipientType.CC, InternetAddress .parse(cc, false)); } if (bcc != null) { msg.setRecipients(Message.RecipientType.BCC, InternetAddress .parse(bcc, false)); } // -- Set the subject and body text -- msg.setSubject(subject); msg.setContent(body.toString(), "text/plain"); // -- Set some other header information -- msg.setHeader("ExpMail", "Expeditee"); msg.setSentDate(new Date()); Transport.send(msg, msg.getRecipients(Message.RecipientType.TO)); } catch (Exception e) { MessageBay.errorMessage("Error sending mail: " + e.getMessage()); return false; } MessageBay.displayMessage("Message sent OK."); return true; } public static void init(Frame settingsFrame) { if (settingsFrame == null) return; if (_theMailSession == null) _theMailSession = new MailSession(settingsFrame); } private class SMTPAuthenticator extends javax.mail.Authenticator { private String _username; private String _password; public SMTPAuthenticator(String username, String password) { _username = username; _password = password; } @Override public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(_username, _password); } } public static MailSession getInstance() { return _theMailSession; } /** * Closes the mail folders. * * @return true if the folders needed to be closed. */ public synchronized boolean finalise() { boolean result = false; try { if (_transport != null && _transport.isConnected()) { _transport.close(); result = true; } if (_folder != null && _folder.isOpen()) { _folder.close(false); result = true; } if (_store != null && _store.isConnected()) { _store.close(); result = true; } } catch (Exception e) { } return result; } public int getUnreadCount() { try { return _folder.getUnreadMessageCount(); } catch (MessagingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return 0; } /** * Gets mail and puts a list of mail items with links to the messages * content. TODO: Put reply and forward button on the frame... * * @param flag * @param isPresent * @param frame * @param point * @return */ public String getMailString(Flag flag, Boolean isPresent) { StringBuffer sb = new StringBuffer(); // -- Get the message wrappers and process them -- Message[] msgs; try { msgs = _folder.getMessages(); for (int msgNum = 0; msgNum < msgs.length; msgNum++) { if (flag == null || msgs[msgNum].getFlags().contains(flag) == isPresent) { if (sb.length() > 0) { sb.append('\n').append('\n').append( "-----------------------------").append('\n') .append('\n'); } // Only get messages that have not been read sb.append(getTextMessage(msgs[msgNum])); } } } catch (MessagingException e) { e.printStackTrace(); } return sb.toString(); } /** * Gets mail and puts a list of mail items with links to the messages * content. TODO: Put reply and forward button on the frame... * * @param flag * @param isPresent * @param frame * @param point * @return */ public Collection getMail(Flag flag, Boolean isPresent, Frame frame, Point point, int numberOfMessages) { if (_folder == null) return null; Collection mailItems = new LinkedList(); // -- Get the message wrappers and process them -- Message[] msgs; try { msgs = _folder.getMessages(); // msgs[0].get int messagesRead = 0; for (int msgNum = msgs.length - 1; messagesRead < numberOfMessages && msgNum >= 0; msgNum--) { if (flag == null || msgs[msgNum].getFlags().contains(flag) == isPresent) { Text newItem = readMessage(msgs[msgNum], msgNum + 1, frame, point); // TODO: May want to reverse the order of mail messages if (newItem != null) { mailItems.add(newItem); point.setY(point.getY() + newItem.getBoundsHeight()); messagesRead++; } else { newItem = null; } } } } catch (MessagingException e) { e.printStackTrace(); } return mailItems; } public Text getMail(Frame frame, Point point, int msgNum) { if (_folder == null) return null; // -- Get the message wrappers and process them -- try { Message[] msgs = _folder.getMessages(); return readMessage(msgs[msgNum], msgNum + 1, frame, point); } catch (ArrayIndexOutOfBoundsException ae) { /* * Just return null... error message will be displayed in the * calling method */ } catch (Exception e) { e.printStackTrace(); } return null; } private Text readMessage(final Message message, final int messageNo, final Frame frame, final Point point) { final Text source = DragAndDropManager.importString("Reading message " + messageNo + "...", point, false); new Thread() { public void run() { try { String subject = message.getSubject(); source.setText("[" + messageNo + "] " + subject); // Create a frameCreator final FrameCreator frames = new FrameCreator(frame .getFramesetName(), frame.getPath(), subject, FrameCreator.ExistingFramesetOptions.AppendSegregatedFrames, false, null); frames.addText("@date: " + message.getSentDate(), null, null, null, false); // Get the header information String from = ((InternetAddress) message.getFrom()[0]) .toString(); Text fromAddressItem = frames.addText("@from: " + from, null, null, null, false); addRecipients(message, frames, _address, RecipientType.TO, "@to: "); addRecipients(message, frames, null, RecipientType.CC, "@cc: "); // Read reply to addresses Text reply = addAddresses(message, frames, from, message .getReplyTo(), "@replyTo: "); /* * If the only person to reply to is the person who sent the * mail add a tag that just says reply */ if (reply == null) { reply = frames.addText("@reply", null, null, null, false); reply.setPosition(10 + fromAddressItem.getX() + fromAddressItem.getBoundsWidth(), fromAddressItem.getY()); } reply.addAction("reply"); // frames.addSpace(15); // -- Get the message part (i.e. the message itself) -- Part messagePart = message; Object content = messagePart.getContent(); // -- or its first body part if it is a multipart // message -- if (content instanceof Multipart) { messagePart = ((Multipart) content).getBodyPart(0); // System.out.println("[ Multipart Message ]"); } // -- Get the content type -- String contentType = messagePart.getContentType() .toLowerCase(); // -- If the content is plain text, we can print it -- // System.out.println("CONTENT:" + contentType); if (contentType.startsWith("text/plain") || contentType.startsWith("text/html")) { InputStream is = messagePart.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(is)); String thisLine = reader.readLine(); StringBuffer nextText = new StringBuffer(); while (thisLine != null) { // A blank line is a signal to start a new text item if (thisLine.trim().equals("")) { addTextItem(frames, nextText.toString()); nextText = new StringBuffer(); } else { nextText.append(thisLine).append('\n'); } thisLine = reader.readLine(); } addTextItem(frames, nextText.toString()); } message.setFlag(Flag.SEEN, true); frames.save(); source.setLink(frames.getName()); DisplayController.requestRefresh(true); } catch (MessageRemovedException mre) { source.setText("Message removed from inbox"); } catch (MessagingException e) { String message = e.getMessage(); if (message == null) { e.printStackTrace(); MessageBay.errorMessage("GetMail error!"); } else { MessageBay.errorMessage("GetMail error: " + message); } } catch (Exception e) { MessageBay.errorMessage("Error reading mail: " + e.getMessage()); e.printStackTrace(); } } /** * @param frames * @param nextText */ private void addTextItem(final FrameCreator frames, String nextText) { nextText = nextText.trim(); if (nextText.length() == 0) return; // Remove the last char if its a newline if (nextText.charAt(nextText.length() - 1) == '\n') nextText = nextText.substring(0, nextText.length() - 1); // TODO: Make the space a setting in frame creator frames.addSpace(10); frames.addText(nextText, null, null, null, false); } }.start(); return source; } /** * "getTextMessage()" method to print a message. */ public String getTextMessage(Message message) { StringBuffer sb = new StringBuffer(); try { // Get the header information String from = ((InternetAddress) message.getFrom()[0]) .getPersonal(); if (from == null) from = ((InternetAddress) message.getFrom()[0]).getAddress(); sb.append("FROM: " + from).append('\n'); String subject = message.getSubject(); sb.append("SUBJECT: " + subject).append('\n').append('\n'); // -- Get the message part (i.e. the message itself) -- Part messagePart = message; Object content = messagePart.getContent(); // -- or its first body part if it is a multipart message -- if (content instanceof Multipart) { messagePart = ((Multipart) content).getBodyPart(0); System.out.println("[ Multipart Message ]"); } // -- Get the content type -- String contentType = messagePart.getContentType(); // -- If the content is plain text, we can print it -- // System.out.println("CONTENT:" + contentType); if (contentType.startsWith("text/plain") || contentType.startsWith("text/html")) { InputStream is = messagePart.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(is)); String thisLine = reader.readLine(); while (thisLine != null) { sb.append(thisLine).append('\n'); thisLine = reader.readLine(); } } message.setFlag(Flag.SEEN, true); } catch (Exception ex) { ex.printStackTrace(); } sb.deleteCharAt(sb.length() - 1); return sb.toString(); } public Folder getFolder() { return _folder; } /** * @param message * @param frames * @param type * @throws MessagingException */ private Text addAddresses(final Message message, final FrameCreator frames, final String excludeAddress, final Address[] addresses, String typeTag) throws MessagingException { if (addresses == null) return null; StringBuffer sb = new StringBuffer(); boolean foundOtherRecipients = false; for (Address addy : addresses) { // Only show the to flag if this message was sent to // other people if (excludeAddress == null || !addy.toString().toLowerCase().contains( excludeAddress.toLowerCase())) { foundOtherRecipients = true; } if (sb.length() > 0) { sb.append(", "); } sb.append(addy.toString()); } Text reply = null; if (foundOtherRecipients) { reply = frames.addText(typeTag + sb.toString(), null, null, null, false); } return reply; } private Text addRecipients(final Message message, final FrameCreator frames, String excludeAddress, RecipientType type, String typeTag) throws MessagingException { // Read and display all the recipients of the message Address[] toRecipients = message.getRecipients(type); return addAddresses(message, frames, excludeAddress, toRecipients, typeTag); } }