Skip to content

Commit

Permalink
Allow to programmatically configure the mailer client
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie committed Jul 10, 2015
1 parent b44494a commit f2f4250
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 48 deletions.
11 changes: 9 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ play.mailer {
}
```

You can also configure the mailer programmatically, see below.

## Usage

### Java
Expand All @@ -61,7 +63,11 @@ public class MyComponent {
// sends text, HTML or both...
email.setBodyText("A text message");
email.setBodyHtml("<html><body><p>An <b>html</b> message</p></body></html>");
mailerClient.send(email);
// configures the mailer before sending the email
Map<String, Object> conf = new HashMap<>();
conf.put("host", "typesafe.org");
conf.put("port", 1234);
mailerClient.configure(new Configuration(conf)).send(email);
}
}
```
Expand Down Expand Up @@ -91,7 +97,8 @@ class MyComponent @Inject() (mailerClient: MailerClient) {
bodyText = Some("A text message"),
bodyHtml = Some("<html><body><p>An <b>html</b> message</p></body></html>")
)
mailerClient.send(email)
// configures the mailer before sending the email
mailer.configure(Configuration.from(Map("host" -> "typesafe.org", "port" -> 1234))).send(email)
}
}
```
Expand Down
16 changes: 15 additions & 1 deletion sample/app/controllers/ApplicationJava.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package controllers;

import org.apache.commons.mail.EmailAttachment;
import play.Configuration;
import play.Play;
import play.api.libs.mailer.MailerClient;
import play.libs.mailer.Email;
Expand All @@ -9,6 +10,8 @@

import javax.inject.Inject;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

public class ApplicationJava extends Controller {

Expand All @@ -31,5 +34,16 @@ public Result send() {
String id = mailer.send(email);
return ok("Email " + id + " sent!");
}


public Result configureAndSend() {
final Email email = new Email();
email.setSubject("Simple email");
email.setFrom("[email protected]");
email.addTo("[email protected]");
Map<String, Object> conf = new HashMap<>();
conf.put("host", "typesafe.org");
conf.put("port", 1234);
String id = mailer.configure(new Configuration(conf)).send(email);
return ok("Email " + id + " sent!");
}
}
7 changes: 7 additions & 0 deletions sample/app/controllers/ApplicationScala.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import java.io.File
import javax.inject.Inject

import org.apache.commons.mail.EmailAttachment
import play.api.Configuration
import play.api.Play.current
import play.api.libs.mailer._
import play.api.mvc.{Action, Controller}
Expand All @@ -25,4 +26,10 @@ class ApplicationScala @Inject()(mailer: MailerClient) extends Controller {
val id = mailer.send(email)
Ok(s"Email $id sent!")
}

def configureAndSend = Action {
val email = Email("Simple email", "[email protected]", Seq("[email protected]"))
val id = mailer.configure(Configuration.from(Map("host" -> "typesafe.org", "port" -> 1234))).send(email)
Ok(s"Email $id sent!")
}
}
2 changes: 1 addition & 1 deletion sample/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ scalaVersion := "2.11.6"
resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases"

libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-mailer" % "3.0.1-SNAPSHOT"
"com.typesafe.play" %% "play-mailer" % "3.0.2-SNAPSHOT"
)

routesGenerator := InjectedRoutesGenerator
Expand Down
7 changes: 5 additions & 2 deletions sample/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
# This file defines all application routes (Higher priority routes first)
# ~~~~

GET /send/java controllers.ApplicationJava.send()
GET /send/scala controllers.ApplicationScala.send()
GET /send/java controllers.ApplicationJava.send()
GET /send/scala controllers.ApplicationScala.send()

GET /configureAndSend/java controllers.ApplicationJava.configureAndSend()
GET /configureAndSend/scala controllers.ApplicationScala.configureAndSend()
10 changes: 10 additions & 0 deletions src/main/java/play/libs/mailer/MailerClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package play.libs.mailer;

import play.Configuration;

/**
* A mailer client.
*/
Expand All @@ -12,4 +14,12 @@ public interface MailerClient {
* @return The message id.
*/
String send(Email email);

/**
* Configure the underlying instance of mailer
*
* @param configuration The configuration
* @return The mailer client
*/
MailerClient configure(Configuration configuration);
}
105 changes: 69 additions & 36 deletions src/main/scala/play/api/libs/mailer/MailerPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.apache.commons.mail._
import play.api.inject._
import play.api.{Configuration, Environment, Logger, PlayConfig}
import play.libs.mailer.{Email => JEmail, MailerClient => JMailerClient}
import play.{Configuration => JConfiguration}

import scala.collection.JavaConverters._

Expand All @@ -28,7 +29,6 @@ object MailerPlugin {
def send(email: Email)(implicit app: play.api.Application) = app.injector.instanceOf[MailerClient].send(email)
}


trait MailerClient extends JMailerClient {

/**
Expand All @@ -39,12 +39,24 @@ trait MailerClient extends JMailerClient {
*/
def send(data: Email): String

/**
* Configure the underlying instance of mailer.
*
* @param configuration configuration
* @return the mailer client
*/
def configure(configuration: Configuration): MailerClient

override def configure(configuration: JConfiguration): JMailerClient = {
configure(Configuration(configuration.underlying()))
}

override def send(data: JEmail): String = {
val email = convert(data)
send(email)
}

protected def convert(data: play.libs.mailer.Email): Email = {
protected def convert(data: JEmail): Email = {
val attachments = data.getAttachments.asScala.map {
case attachment =>
if (Option(attachment.getFile).isDefined) {
Expand Down Expand Up @@ -76,47 +88,35 @@ trait MailerClient extends JMailerClient {
}
}



// Implementations

class CommonsMailer @Inject()(configuration: Configuration) extends MailerClient {

private val mailerConfig = PlayConfig(configuration).getDeprecatedWithFallback("play.mailer", "smtp")
private lazy val mock = mailerConfig.get[Boolean]("mock")
private val defaultConfig = PlayConfig(configuration).getDeprecatedWithFallback("play.mailer", "smtp")
private lazy val mock = defaultConfig.get[Boolean]("mock")

private lazy val instance = {
if (mock) {
new MockMailer()
} else {
val smtpHost = mailerConfig.getOptional[String]("host").getOrElse(throw new RuntimeException("play.mailer.host needs to be set in application.conf in order to use this plugin (or set play.mailer.mock to true)"))
val smtpPort = mailerConfig.get[Int]("port")
val smtpSsl = mailerConfig.get[Boolean]("ssl")
val smtpTls = mailerConfig.get[Boolean]("tls")
val smtpUser = mailerConfig.getOptional[String]("user")
val smtpPassword = mailerConfig.getOptional[String]("password")
val debugMode = mailerConfig.get[Boolean]("debug")
val smtpTimeout = mailerConfig.getOptional[Int]("timeout")
val smtpConnectionTimeout = mailerConfig.getOptional[Int]("connectiontimeout")
new SMTPMailer(smtpHost, smtpPort, smtpSsl, smtpTls, smtpUser, smtpPassword, debugMode, smtpTimeout, smtpConnectionTimeout) {
new SMTPMailer(defaultConfig) {
override def send(email: MultiPartEmail): String = email.send()
override def createMultiPartEmail(): MultiPartEmail = new MultiPartEmail()
override def createHtmlEmail(): HtmlEmail = new HtmlEmail()
}
}
}

override def send(data: Email): String = instance.send(data)
override def send(data: Email): String = {
instance.send(data)
}

override def configure(configuration: Configuration) = {
instance.configure(configuration)
}
}

abstract class SMTPMailer(smtpHost: String, smtpPort: Int,
smtpSsl: Boolean,
smtpTls: Boolean,
smtpUser: Option[String],
smtpPass: Option[String],
debugMode: Boolean,
smtpTimeout: Option[Int],
smtpConnectionTimeout: Option[Int]) extends MailerClient {
abstract class SMTPMailer(defaultConfig: PlayConfig, var config: Option[SMTPConfiguration] = None) extends MailerClient {

def send(email: MultiPartEmail): String

Expand All @@ -126,7 +126,13 @@ abstract class SMTPMailer(smtpHost: String, smtpPort: Int,

override def send(data: Email): String = send(createEmail(data))

override def configure(configuration: Configuration) = {
config = Some(SMTPConfiguration(PlayConfig(Configuration.reference.getConfig("play.mailer").get ++ configuration)))
this
}

def createEmail(data: Email): MultiPartEmail = {
val conf = config.getOrElse(SMTPConfiguration(defaultConfig))
val email = createEmail(data.bodyText, data.bodyHtml, data.charset.getOrElse("utf-8"))
email.setSubject(data.subject)
setAddress(data.from) { (address, name) => email.setFrom(address, name) }
Expand All @@ -138,8 +144,8 @@ abstract class SMTPMailer(smtpHost: String, smtpPort: Int,
data.headers.foreach {
header => email.addHeader(header._1, header._2)
}
smtpTimeout.foreach(email.setSocketTimeout)
smtpConnectionTimeout.foreach(email.setSocketConnectionTimeout)
conf.timeout.foreach(email.setSocketTimeout)
conf.connectionTimeout.foreach(email.setSocketConnectionTimeout)
data.attachments.foreach {
case attachmentData: AttachmentData =>
val description = attachmentData.description.getOrElse(attachmentData.name)
Expand All @@ -156,16 +162,16 @@ abstract class SMTPMailer(smtpHost: String, smtpPort: Int,
emailAttachment.setDisposition(disposition)
email.attach(emailAttachment)
}
email.setHostName(smtpHost)
email.setSmtpPort(smtpPort)
email.setSSLOnConnect(smtpSsl)
if (smtpSsl) {
email.setSslSmtpPort(smtpPort.toString)
email.setHostName(conf.host)
email.setSmtpPort(conf.port)
email.setSSLOnConnect(conf.ssl)
if (conf.ssl) {
email.setSslSmtpPort(conf.port.toString)
}
email.setStartTLSEnabled(smtpTls)
for (u <- smtpUser; p <- smtpPass) yield email.setAuthenticator(new DefaultAuthenticator(u, p))
if (debugMode && Logger.isDebugEnabled) {
email.setDebug(debugMode)
email.setStartTLSEnabled(conf.tls)
for (u <- conf.user; p <- conf.password) yield email.setAuthenticator(new DefaultAuthenticator(u, p))
if (conf.debugMode && Logger.isDebugEnabled) {
email.setDebug(conf.debugMode)
email.getMailSession.setDebugOut(new PrintStream(new FilterOutputStream(null) {
override def write(b: Array[Byte]) {
Logger.debug(new String(b))
Expand Down Expand Up @@ -241,6 +247,8 @@ class MockMailer @Inject() extends MailerClient {
email.headers.foreach(header => Logger.info(s"header: $header"))
""
}

override def configure(configuration: Configuration) = this
}

sealed trait Attachment
Expand Down Expand Up @@ -268,3 +276,28 @@ case class AttachmentData(name: String,
mimetype: String,
description: Option[String] = None,
disposition: Option[String] = None) extends Attachment

case class SMTPConfiguration(host: String,
port: Int,
ssl: Boolean = false,
tls: Boolean = false,
user: Option[String],
password: Option[String],
debugMode: Boolean = false,
timeout: Option[Int] = None,
connectionTimeout: Option[Int] = None)

object SMTPConfiguration {

def apply(config: PlayConfig) = new SMTPConfiguration(
config.getOptional[String]("host").getOrElse(throw new RuntimeException("host needs to be set in order to use this plugin (or set play.mailer.mock to true in application.conf)")),
config.get[Int]("port"),
config.get[Boolean]("ssl"),
config.get[Boolean]("tls"),
config.getOptional[String]("user"),
config.getOptional[String]("password"),
config.get[Boolean]("debug"),
config.getOptional[Int]("timeout"),
config.getOptional[Int]("connectiontimeout")
)
}
35 changes: 29 additions & 6 deletions src/test/scala/play/api/libs/mailer/MailerPluginSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import javax.mail.Part

import org.apache.commons.mail.{EmailConstants, HtmlEmail, MultiPartEmail}
import org.specs2.mutable._
import play.api.{PlayConfig, Configuration}
import play.api.test._

class MailerPluginSpec extends Specification {

object SimpleMailerClient extends MailerClient {
override def send(data: Email): String = ""
override def convert(data: play.libs.mailer.Email) = super.convert(data)
override def configure(configuration: Configuration): MailerClient = this
}
class MockMultiPartEmail extends MultiPartEmail {
override def getPrimaryBodyPart = super.getPrimaryBodyPart
Expand All @@ -26,7 +28,7 @@ class MailerPluginSpec extends Specification {
object MockSMTPMailer extends MockSMTPMailerWithTimeouts(None, None)

class MockSMTPMailerWithTimeouts(smtpTimeout: Option[Int], smtpConnectionTimeout: Option[Int])
extends SMTPMailer("typesafe.org", 1234, true, false, Some("user"), Some("password"), false, smtpTimeout, smtpConnectionTimeout) {
extends SMTPMailer(PlayConfig(Configuration.empty), Some(SMTPConfiguration("typesafe.org", 1234, ssl = true, tls = false, Some("user"), Some("password"), debugMode = false, smtpTimeout, smtpConnectionTimeout))) {
override def send(email: MultiPartEmail) = ""
override def createMultiPartEmail(): MultiPartEmail = new MockMultiPartEmail
override def createHtmlEmail(): HtmlEmail = new MockHtmlEmail
Expand All @@ -47,6 +49,27 @@ class MailerPluginSpec extends Specification {
email.getMailSession.getProperty("mail.debug") mustEqual "false"
}

"reconfigure SMTP" in {
val mailer = MockSMTPMailer
mailer.configure(Configuration.from(Map(
"host" -> "playframework.com",
"port" -> 5678,
"ssl" -> false,
"tls" -> true
)))
val email = mailer.createEmail(Email(
subject = "Subject",
from = "James Roper <[email protected]>"
))
email.getSmtpPort mustEqual "5678"
// Default value
email.getSslSmtpPort mustEqual "465"
email.getMailSession.getProperty("mail.smtp.auth") must beNull
email.getMailSession.getProperty("mail.smtp.host") mustEqual "playframework.com"
email.getMailSession.getProperty("mail.smtp.starttls.enable") mustEqual "true"
email.getMailSession.getProperty("mail.debug") mustEqual "false"
}

"configure the SMTP timeouts if configured" in {
val mailer = new MockSMTPMailerWithTimeouts(Some(10), Some(99))
val email = mailer.createEmail(Email(
Expand Down Expand Up @@ -208,11 +231,11 @@ class MailerPluginSpec extends Specification {
convert.headers.size mustEqual 1
convert.headers.head mustEqual ("key", "value")
convert.attachments.size mustEqual 2
convert.attachments(0) must beAnInstanceOf[AttachmentFile]
convert.attachments(0).asInstanceOf[AttachmentFile].name mustEqual "play icon"
convert.attachments(0).asInstanceOf[AttachmentFile].file mustEqual getPlayIcon
convert.attachments(0).asInstanceOf[AttachmentFile].description mustEqual Some("A beautiful icon")
convert.attachments(0).asInstanceOf[AttachmentFile].disposition mustEqual Some(Part.ATTACHMENT)
convert.attachments.head must beAnInstanceOf[AttachmentFile]
convert.attachments.head.asInstanceOf[AttachmentFile].name mustEqual "play icon"
convert.attachments.head.asInstanceOf[AttachmentFile].file mustEqual getPlayIcon
convert.attachments.head.asInstanceOf[AttachmentFile].description mustEqual Some("A beautiful icon")
convert.attachments.head.asInstanceOf[AttachmentFile].disposition mustEqual Some(Part.ATTACHMENT)
convert.attachments(1) must beAnInstanceOf[AttachmentData]
convert.attachments(1).asInstanceOf[AttachmentData].name mustEqual "data.txt"
convert.attachments(1).asInstanceOf[AttachmentData].data mustEqual "data".getBytes
Expand Down

0 comments on commit f2f4250

Please sign in to comment.