package play.libs; import org.apache.commons.lang.StringUtils; import org.apache.commons.mail.Email; import org.apache.commons.mail.EmailException; import play.Logger; import play.Play; import play.exceptions.MailException; import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.*; /** * Mail utils */ public class Mail { public static Session session; public static boolean asynchronousSend = true; /** * Send an email */ public static Future<Boolean> send(Email email) { try { email = buildMessage(email); if (Play.configuration.getProperty("mail.smtp", "").equals("mock") && Play.mode == Play.Mode.DEV) { Mock.send(email); return new Future<Boolean>() { public boolean cancel(boolean mayInterruptIfRunning) { return false; } public boolean isCancelled() { return false; } public boolean isDone() { return true; } public Boolean get() throws InterruptedException, ExecutionException { return true; } public Boolean get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return true; } }; } email.setMailSession(getSession()); return sendMessage(email); } catch (EmailException ex) { throw new MailException("Cannot send email", ex); } } /** * */ public static Email buildMessage(Email email) throws EmailException { String from = Play.configuration.getProperty("mail.smtp.from"); if (email.getFromAddress() == null && !StringUtils.isEmpty(from)) { email.setFrom(from); } else if (email.getFromAddress() == null) { throw new MailException("Please define a 'from' email address", new NullPointerException()); } if ((email.getToAddresses() == null || email.getToAddresses().size() == 0) && (email.getCcAddresses() == null || email.getCcAddresses().size() == 0) && (email.getBccAddresses() == null || email.getBccAddresses().size() == 0)) { throw new MailException("Please define a recipient email address", new NullPointerException()); } if (email.getSubject() == null) { throw new MailException("Please define a subject", new NullPointerException()); } if (email.getReplyToAddresses() == null || email.getReplyToAddresses().size() == 0) { email.addReplyTo(email.getFromAddress().getAddress()); } return email; } public static Session getSession() { if (session == null) { Properties props = new Properties(); // Put a bogus value even if we are on dev mode, otherwise JavaMail will complain props.put("mail.smtp.host", Play.configuration.getProperty("mail.smtp.host", "localhost")); String channelEncryption; if (Play.configuration.containsKey("mail.smtp.protocol") && Play.configuration.getProperty("mail.smtp.protocol", "smtp").equals("smtps")) { // Backward compatibility before stable5 channelEncryption = "starttls"; } else { channelEncryption = Play.configuration.getProperty("mail.smtp.channel", "clear"); } if (channelEncryption.equals("clear")) { props.put("mail.smtp.port", "25"); } else if (channelEncryption.equals("ssl")) { // port 465 + setup yes ssl socket factory (won't verify that the server certificate is signed with a root ca.) props.put("mail.smtp.port", "465"); props.put("mail.smtp.socketFactory.port", "465"); props.put("mail.smtp.socketFactory.class", "play.utils.YesSSLSocketFactory"); props.put("mail.smtp.socketFactory.fallback", "false"); } else if (channelEncryption.equals("starttls")) { // port 25 + enable starttls + ssl socket factory props.put("mail.smtp.port", "25"); props.put("mail.smtp.starttls.enable", "true"); // can't install our socket factory. will work only with server that has a signed certificate // story to be continued in javamail 1.4.2 : https://glassfish.dev.java.net/issues/show_bug.cgi?id=5189 } if (Play.configuration.containsKey("mail.smtp.localhost")) { props.put("mail.smtp.localhost", Play.configuration.get("mail.smtp.localhost")); //override defaults } if (Play.configuration.containsKey("mail.smtp.socketFactory.class")) { props.put("mail.smtp.socketFactory.class", Play.configuration.get("mail.smtp.socketFactory.class")); } if (Play.configuration.containsKey("mail.smtp.port")) { props.put("mail.smtp.port", Play.configuration.get("mail.smtp.port")); } String user = Play.configuration.getProperty("mail.smtp.user"); String password = Play.configuration.getProperty("mail.smtp.pass"); if (password == null) { // Fallback to old convention password = Play.configuration.getProperty("mail.smtp.password"); } String authenticator = Play.configuration.getProperty("mail.smtp.authenticator"); session = null; if (authenticator != null) { props.put("mail.smtp.auth", "true"); try { session = Session.getInstance(props, (Authenticator) Play.classloader.loadClass(authenticator).newInstance()); } catch (Exception e) { Logger.error(e, "Cannot instanciate custom SMTP authenticator (%s)", authenticator); } } if (session == null) { if (user != null && password != null) { props.put("mail.smtp.auth", "true"); session = Session.getInstance(props, new SMTPAuthenticator(user, password)); } else { props.remove("mail.smtp.auth"); session = Session.getInstance(props); } } if (Boolean.parseBoolean(Play.configuration.getProperty("mail.debug", "false"))) { session.setDebug(true); } } return session; } /** * Send a JavaMail message * * @param msg An Email message */ public static Future<Boolean> sendMessage(final Email msg) { if (asynchronousSend) { return executor.submit(new Callable<Boolean>() { public Boolean call() { try { msg.setSentDate(new Date()); msg.send(); return true; } catch (Throwable e) { MailException me = new MailException("Error while sending email", e); Logger.error(me, "The email has not been sent"); return false; } } }); } else { final StringBuffer result = new StringBuffer(); try { msg.setSentDate(new Date()); msg.send(); } catch (Throwable e) { MailException me = new MailException("Error while sending email", e); Logger.error(me, "The email has not been sent"); result.append("oops"); } return new Future<Boolean>() { public boolean cancel(boolean mayInterruptIfRunning) { return false; } public boolean isCancelled() { return false; } public boolean isDone() { return true; } public Boolean get() throws InterruptedException, ExecutionException { return result.length() == 0; } public Boolean get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return result.length() == 0; } }; } } static ExecutorService executor = Executors.newCachedThreadPool(); public static class SMTPAuthenticator extends Authenticator { private String user; private String password; public SMTPAuthenticator(String user, String password) { this.user = user; this.password = password; } @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, password); } } public static class Mock { static Map<String, String> emails = new HashMap<String, String>(); public static String getContent(Part message) throws MessagingException, IOException { if (message.getContent() instanceof String) { return message.getContentType() + ": " + message.getContent() + " \n\t"; } else if (message.getContent() != null && message.getContent() instanceof Multipart) { Multipart part = (Multipart) message.getContent(); String text = ""; for (int i = 0; i < part.getCount(); i++) { BodyPart bodyPart = part.getBodyPart(i); if (!Message.ATTACHMENT.equals(bodyPart.getDisposition())) { text += getContent(bodyPart); } else { text += "attachment: \n" + "\t\t name: " + (StringUtils.isEmpty(bodyPart.getFileName()) ? "none" : bodyPart.getFileName()) + "\n" + "\t\t disposition: " + bodyPart.getDisposition() + "\n" + "\t\t description: " + (StringUtils.isEmpty(bodyPart.getDescription()) ? "none" : bodyPart.getDescription()) + "\n\t"; } } return text; } if (message.getContent() != null && message.getContent() instanceof Part) { if (!Message.ATTACHMENT.equals(message.getDisposition())) { return getContent((Part) message.getContent()); } else { return "attachment: \n" + "\t\t name: " + (StringUtils.isEmpty(message.getFileName()) ? "none" : message.getFileName()) + "\n" + "\t\t disposition: " + message.getDisposition() + "\n" + "\t\t description: " + (StringUtils.isEmpty(message.getDescription()) ? "none" : message.getDescription()) + "\n\t"; } } return ""; } static void send(Email email) { try { final StringBuffer content = new StringBuffer(); Properties props = new Properties(); props.put("mail.smtp.host", "myfakesmtpserver.com"); Session session = Session.getInstance(props); email.setMailSession(session); email.buildMimeMessage(); MimeMessage msg = email.getMimeMessage(); msg.saveChanges(); String body = getContent(msg); content.append("From Mock Mailer\n\tNew email received by"); content.append("\n\tFrom: " + email.getFromAddress().getAddress()); content.append("\n\tReplyTo: " + ((InternetAddress) email.getReplyToAddresses().get(0)).getAddress()); content.append("\n\tTo: "); for (Object add : email.getToAddresses()) { content.append(add.toString() + ", "); } // remove the last , content.delete(content.length() - 2, content.length()); if (email.getCcAddresses() != null && !email.getCcAddresses().isEmpty()) { content.append("\n\tCc: "); for (Object add : email.getCcAddresses()) { content.append(add.toString() + ", "); } // remove the last , content.delete(content.length() - 2, content.length()); } if (email.getBccAddresses() != null && !email.getBccAddresses().isEmpty()) { content.append("\n\tBcc: "); for (Object add : email.getBccAddresses()) { content.append(add.toString() + ", "); } // remove the last , content.delete(content.length() - 2, content.length()); } content.append("\n\tSubject: " + email.getSubject()); content.append("\n\t" + body); content.append("\n"); Logger.info(content.toString()); for (Object add : email.getToAddresses()) { content.append(", " + add.toString()); emails.put(((InternetAddress) add).getAddress(), content.toString()); } } catch (Exception e) { Logger.error(e, "error sending mock email"); } } public static String getLastMessageReceivedBy(String email) { return emails.get(email); } public static void reset(){ emails.clear(); } } }