diff --git a/README.adoc b/README.adoc index f6661cd..c9af225 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,9 @@ # About :next-branch-uri: https://github.com/playframework/play-mailer/tree/next +:playframework-24x-docs-uri: https://www.playframework.com/documentation/2.4.x/ +:runtime-di-uri: {playframework-24x-docs-uri}/ScalaDependencyInjection +:compile-time-di-uri: {playframework-24x-docs-uri}/ScalaCompileTimeDependencyInjection + This plugin provides a simple emailer. //ifdef::env-github[] @@ -35,7 +39,7 @@ play.mailer { } ``` -You can also configure the mailer programmatically, see below. +You can also configure the mailer programmatically, see https://github.com/playframework/play-mailer/blob/master/user-manual.adoc[user manual]. ## Usage @@ -63,11 +67,7 @@ public class MyComponent { // sends text, HTML or both... .setBodyText("A text message") .setBodyHtml("

An html message

"); - // configures the mailer before sending the email - Map conf = new HashMap<>(); - conf.put("host", "typesafe.org"); - conf.put("port", 1234); - mailerClient.configure(new Configuration(conf)).send(email); + mailerClient.send(email); } } ``` @@ -77,6 +77,10 @@ public class MyComponent { You need a `MailerClient` instance to send an email : ```scala +import play.api.libs.mailer._ +import java.io.File +import org.apache.commons.mail.EmailAttachment + def sendEmail { val email = Email( "Simple email", @@ -92,12 +96,11 @@ def sendEmail { bodyText = Some("A text message"), bodyHtml = Some("

An html message

") ) - // configures the mailer before sending the email - mailerClient.configure(Configuration.from(Map("host" -> "typesafe.org", "port" -> 1234))).send(email) + mailerClient.send(email) } ``` -Since Play 2.4, you can use runtime dependency injection or compile time dependency injection. +Since Play 2.4, you can use {runtime-di-uri}[runtime dependency injection] or {compile-time-di-uri}[compile time dependency injection]. As a result, the `MailerClient` instance can be obtained using runtime dependency injection or compile time dependency injection. #### With runtime injection @@ -105,7 +108,10 @@ As a result, the `MailerClient` instance can be obtained using runtime dependenc Use the `@Inject` annotation on the constructor of your component or controller: ```scala -class MyComponent @Inject() (mailerClient: MailerClient) +import play.api.libs.mailer._ +import javax.inject.Inject + +class MyComponent @Inject() (mailerClient: MailerClient) ``` #### With compile time injection @@ -113,6 +119,8 @@ class MyComponent @Inject() (mailerClient: MailerClient) Declare the `MailerClient` without the `@Inject` annotation: ```scala +import play.api.libs.mailer._ + class MyComponent(mailerClient: MailerClient) ``` diff --git a/samples/runtimeDI/app/controllers/ApplicationJava.java b/samples/runtimeDI/app/controllers/ApplicationJava.java index 1ba05f4..25f3549 100644 --- a/samples/runtimeDI/app/controllers/ApplicationJava.java +++ b/samples/runtimeDI/app/controllers/ApplicationJava.java @@ -1,7 +1,6 @@ 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; @@ -10,8 +9,6 @@ import javax.inject.Inject; import java.io.File; -import java.util.HashMap; -import java.util.Map; public class ApplicationJava extends Controller { @@ -34,16 +31,4 @@ public Result send() { String id = mailer.send(email); return ok("Email " + id + " sent!"); } - - public Result configureAndSend() { - final Email email = new Email() - .setSubject("Simple email") - .setFrom("from@email.com") - .addTo("to@email.com"); - Map 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!"); - } } diff --git a/samples/runtimeDI/app/controllers/ApplicationScala.scala b/samples/runtimeDI/app/controllers/ApplicationScala.scala index e4bc837..faa432a 100644 --- a/samples/runtimeDI/app/controllers/ApplicationScala.scala +++ b/samples/runtimeDI/app/controllers/ApplicationScala.scala @@ -4,7 +4,6 @@ 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} @@ -27,9 +26,9 @@ class ApplicationScala @Inject()(mailer: MailerClient) extends Controller { Ok(s"Email $id sent!") } - def configureAndSend = Action { - val email = Email("Simple email", "from@email.com", Seq("to@email.com")) - val id = mailer.configure(Configuration.from(Map("host" -> "typesafe.org", "port" -> 1234))).send(email) + def sendWithCustomMailer = Action { + val mailer = new SMTPMailer(SMTPConfiguration("typesafe.org", 1234)) + val id = mailer.send(Email("Simple email", "Mister FROM ")) Ok(s"Email $id sent!") } } diff --git a/samples/runtimeDI/app/controllers/CustomSMTPConfigurationProvider.scala b/samples/runtimeDI/app/controllers/CustomSMTPConfigurationProvider.scala new file mode 100644 index 0000000..853e6c1 --- /dev/null +++ b/samples/runtimeDI/app/controllers/CustomSMTPConfigurationProvider.scala @@ -0,0 +1,17 @@ +package controllers + +import javax.inject.Provider + +import play.api.libs.mailer.SMTPConfiguration +import play.api.{Configuration, Environment} +import play.api.inject.Module + +class CustomSMTPConfigurationProvider extends Provider[SMTPConfiguration] { + override def get() = new SMTPConfiguration("typesafe.org", 1234) +} + +class CustomMailerConfigurationModule extends Module { + def bindings(environment: Environment, configuration: Configuration) = Seq( + bind[SMTPConfiguration].toProvider[CustomSMTPConfigurationProvider] + ) +} \ No newline at end of file diff --git a/samples/runtimeDI/conf/application.conf b/samples/runtimeDI/conf/application.conf index f428172..40cb83a 100644 --- a/samples/runtimeDI/conf/application.conf +++ b/samples/runtimeDI/conf/application.conf @@ -32,3 +32,9 @@ logger.application=DEBUG # debug=false # mock=false #} +#play { +# modules { +# disabled += "play.api.libs.mailer.SMTPConfigurationModule" +# enabled += "controllers.CustomMailerConfigurationModule" +# } +#} diff --git a/samples/runtimeDI/conf/routes b/samples/runtimeDI/conf/routes index 4641222..ac3eeb1 100644 --- a/samples/runtimeDI/conf/routes +++ b/samples/runtimeDI/conf/routes @@ -2,8 +2,7 @@ # 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() +GET /send/scala/customMailer controllers.ApplicationScala.sendWithCustomMailer() diff --git a/src/main/java/play/libs/mailer/MailerClient.java b/src/main/java/play/libs/mailer/MailerClient.java index 1cd6483..10d14e0 100644 --- a/src/main/java/play/libs/mailer/MailerClient.java +++ b/src/main/java/play/libs/mailer/MailerClient.java @@ -14,12 +14,4 @@ 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); } diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 995d1ca..80fe958 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -1,6 +1,7 @@ play { modules { enabled += "play.api.libs.mailer.MailerModule" + enabled += "play.api.libs.mailer.SMTPConfigurationModule" } mailer { host = null diff --git a/src/main/scala/play/api/libs/mailer/MailerPlugin.scala b/src/main/scala/play/api/libs/mailer/MailerPlugin.scala index 3ab05c4..cb4ac59 100644 --- a/src/main/scala/play/api/libs/mailer/MailerPlugin.scala +++ b/src/main/scala/play/api/libs/mailer/MailerPlugin.scala @@ -1,33 +1,38 @@ package play.api.libs.mailer import java.io.{File, FilterOutputStream, PrintStream} -import javax.inject.Inject +import javax.inject.{Inject, Provider} import javax.mail.internet.InternetAddress 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._ // for compile-time injection trait MailerComponents { def configuration: Configuration - lazy val mailerClient = new CommonsMailer(configuration) + lazy val mailerClient = new SMTPMailer(new SMTPConfigurationProvider(configuration).get()) } // for runtime injection class MailerModule extends Module { def bindings(environment: Environment, configuration: Configuration) = Seq( - bind[MailerClient].to[CommonsMailer], + bind[MailerClient].to[SMTPMailer], bind[JMailerClient].to(bind[MailerClient]), bind[MailerClient].qualifiedWith("mock").to[MockMailer], bind[JMailerClient].qualifiedWith("mock").to[MockMailer] ) } +class SMTPConfigurationModule extends Module { + def bindings(environment: Environment, configuration: Configuration) = Seq( + bind[SMTPConfiguration].toProvider[SMTPConfigurationProvider] + ) +} + // API @deprecated("Use injected MailerClient instead", "2.4.0") @@ -46,18 +51,6 @@ 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) @@ -97,16 +90,13 @@ trait MailerClient extends JMailerClient { // Implementations -class CommonsMailer @Inject()(configuration: Configuration) extends MailerClient { - - private val defaultConfig = PlayConfig(configuration).getDeprecatedWithFallback("play.mailer", "smtp") - private lazy val mock = defaultConfig.get[Boolean]("mock") +class SMTPMailer @Inject() (smtpConfiguration: SMTPConfiguration) extends MailerClient { private lazy val instance = { - if (mock) { + if (smtpConfiguration.mock) { new MockMailer() } else { - new SMTPMailer(defaultConfig) { + new CommonsMailer(smtpConfiguration) { override def send(email: MultiPartEmail): String = email.send() override def createMultiPartEmail(): MultiPartEmail = new MultiPartEmail() override def createHtmlEmail(): HtmlEmail = new HtmlEmail() @@ -117,13 +107,9 @@ class CommonsMailer @Inject()(configuration: Configuration) extends MailerClient override def send(data: Email): String = { instance.send(data) } - - override def configure(configuration: Configuration) = { - instance.configure(configuration) - } } -abstract class SMTPMailer(defaultConfig: PlayConfig, var config: Option[SMTPConfiguration] = None) extends MailerClient { +abstract class CommonsMailer(conf: SMTPConfiguration) extends MailerClient { def send(email: MultiPartEmail): String @@ -133,13 +119,7 @@ abstract class SMTPMailer(defaultConfig: PlayConfig, var config: Option[SMTPConf 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) } @@ -201,18 +181,18 @@ abstract class SMTPMailer(defaultConfig: PlayConfig, var config: Option[SMTPConf */ private def createEmail(bodyText: Option[String], bodyHtml: Option[String], charset: String): MultiPartEmail = { (bodyHtml.filter(_.trim.nonEmpty), bodyText.filter(_.trim.nonEmpty)) match { - case (Some(bodyHtml), bodyTextOpt) => + case (Some(htmlMsg), bodyTextOpt) => val htmlEmail = createHtmlEmail() htmlEmail.setCharset(charset) - htmlEmail.setHtmlMsg(bodyHtml) + htmlEmail.setHtmlMsg(htmlMsg) bodyTextOpt.foreach { bodyText => htmlEmail.setTextMsg(bodyText) } htmlEmail - case (None, Some(bodyText)) => + case (None, Some(msg)) => val multiPartEmail = createMultiPartEmail() multiPartEmail.setCharset(charset) - multiPartEmail.setMsg(bodyText) + multiPartEmail.setMsg(msg) multiPartEmail case _ => createMultiPartEmail() @@ -254,8 +234,6 @@ class MockMailer @Inject() extends MailerClient { email.headers.foreach(header => Logger.info(s"header: $header")) "" } - - override def configure(configuration: Configuration) = this } sealed trait Attachment @@ -288,16 +266,17 @@ case class SMTPConfiguration(host: String, port: Int, ssl: Boolean = false, tls: Boolean = false, - user: Option[String], - password: Option[String], + user: Option[String] = None, + password: Option[String] = None, debugMode: Boolean = false, timeout: Option[Int] = None, - connectionTimeout: Option[Int] = None) + connectionTimeout: Option[Int] = None, + mock: Boolean = false) 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)")), + resolveHost(config), config.get[Int]("port"), config.get[Boolean]("ssl"), config.get[Boolean]("tls"), @@ -305,6 +284,29 @@ object SMTPConfiguration { config.getOptional[String]("password"), config.get[Boolean]("debug"), config.getOptional[Int]("timeout"), - config.getOptional[Int]("connectiontimeout") + config.getOptional[Int]("connectiontimeout"), + config.get[Boolean]("mock") + ) + + def resolveHost(config: PlayConfig) = { + if (config.get[Boolean]("mock")) { + // host won't be used anyway... + "" + } else { + 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)")) + } + } +} + +class SMTPConfigurationProvider @Inject()(configuration: Configuration) extends Provider[SMTPConfiguration] { + override def get() = { + val config = PlayConfig(configuration).getDeprecatedWithFallback("play.mailer", "smtp") + SMTPConfiguration(config) + } +} + +class MailerConfigurationModule extends Module { + def bindings(environment: Environment, configuration: Configuration) = Seq( + bind[SMTPConfiguration].toProvider[SMTPConfigurationProvider] ) } \ No newline at end of file diff --git a/src/test/scala/play/api/libs/mailer/MailerPluginSpec.scala b/src/test/scala/play/api/libs/mailer/MailerPluginSpec.scala index 1942532..ccb309a 100644 --- a/src/test/scala/play/api/libs/mailer/MailerPluginSpec.scala +++ b/src/test/scala/play/api/libs/mailer/MailerPluginSpec.scala @@ -5,7 +5,6 @@ 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 { @@ -13,7 +12,6 @@ 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 @@ -25,10 +23,10 @@ class MailerPluginSpec extends Specification { override def getPrimaryBodyPart = super.getPrimaryBodyPart override def getContainer = super.getContainer } - object MockSMTPMailer extends MockSMTPMailerWithTimeouts(None, None) + object MockCommonsMailer extends MockCommonsMailerWithTimeouts(None, None) - class MockSMTPMailerWithTimeouts(smtpTimeout: Option[Int], smtpConnectionTimeout: Option[Int]) - extends SMTPMailer(PlayConfig(Configuration.empty), Some(SMTPConfiguration("typesafe.org", 1234, ssl = true, tls = false, Some("user"), Some("password"), debugMode = false, smtpTimeout, smtpConnectionTimeout))) { + class MockCommonsMailerWithTimeouts(smtpTimeout: Option[Int], smtpConnectionTimeout: Option[Int]) + extends CommonsMailer(SMTPConfiguration("typesafe.org", 1234, ssl = true, tls = false, Some("user"), Some("password"), debugMode = false, smtpTimeout, smtpConnectionTimeout, mock = false)) { override def send(email: MultiPartEmail) = "" override def createMultiPartEmail(): MultiPartEmail = new MockMultiPartEmail override def createHtmlEmail(): HtmlEmail = new MockHtmlEmail @@ -36,7 +34,7 @@ class MailerPluginSpec extends Specification { "The CommonsMailer" should { "configure SMTP" in { - val mailer = MockSMTPMailer + val mailer = MockCommonsMailer val email = mailer.createEmail(Email( subject = "Subject", from = "James Roper " @@ -49,29 +47,8 @@ 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.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 mailer = new MockCommonsMailerWithTimeouts(Some(10), Some(99)) val email = mailer.createEmail(Email( subject = "Subject", from = "James Roper " @@ -81,7 +58,7 @@ class MailerPluginSpec extends Specification { } "leave default SMTP timeouts if they are not configured" in { - val mailer = new MockSMTPMailerWithTimeouts(None, None) + val mailer = new MockCommonsMailerWithTimeouts(None, None) val email = mailer.createEmail(Email( subject = "Subject", from = "James Roper " @@ -91,7 +68,7 @@ class MailerPluginSpec extends Specification { } "create an empty email" in { - val mailer = MockSMTPMailer + val mailer = MockCommonsMailer val messageId = mailer.send(Email( subject = "Subject", from = "James Roper ", @@ -101,7 +78,7 @@ class MailerPluginSpec extends Specification { } "create a simple email" in { - val mailer = MockSMTPMailer + val mailer = MockCommonsMailer val email = mailer.createEmail(Email( subject = "Subject", from = "James Roper ", @@ -117,7 +94,7 @@ class MailerPluginSpec extends Specification { } "create a simple email with attachment" in { - val mailer = MockSMTPMailer + val mailer = MockCommonsMailer val email = mailer.createEmail(Email( subject = "Subject", from = "James Roper ", @@ -140,7 +117,7 @@ class MailerPluginSpec extends Specification { } "create a simple email with inline attachment and description" in { - val mailer = MockSMTPMailer + val mailer = MockCommonsMailer val email = mailer.createEmail(Email( subject = "Subject", from = "James Roper ", @@ -163,7 +140,7 @@ class MailerPluginSpec extends Specification { } "set address with name" in { - val mailer = MockSMTPMailer + val mailer = MockCommonsMailer val email = mailer.createEmail(Email( subject = "Subject", from = "James Roper ", @@ -182,7 +159,7 @@ class MailerPluginSpec extends Specification { } "set address without name" in { - val mailer = MockSMTPMailer + val mailer = MockCommonsMailer val email = mailer.createEmail(Email( subject = "Subject", from = "jroper@typesafe.com", @@ -248,11 +225,18 @@ class MailerPluginSpec extends Specification { import play.libs.mailer.{MailerClient => JMailerClient} import play.api.inject.bind - "provide the Scala mailer client" in new WithApplication() { - app.injector.instanceOf[MailerClient] must beAnInstanceOf[CommonsMailer] + val applicationWithMinimalMailerConfiguration = FakeApplication(additionalConfiguration = Map("play.mailer.host" -> "typesafe.org", "play.mailer.port" -> 25)) + val applicationWithDeprecatedMailerConfiguration = FakeApplication(additionalConfiguration = Map("smtp.host" -> "typesafe.org", "smtp.port" -> 25)) + + "provide the Scala mailer client" in new WithApplication(applicationWithMinimalMailerConfiguration) { + app.injector.instanceOf[MailerClient] must beAnInstanceOf[SMTPMailer] + } + "provide the Java mailer client" in new WithApplication(applicationWithMinimalMailerConfiguration) { + app.injector.instanceOf[JMailerClient] must beAnInstanceOf[SMTPMailer] } - "provide the Java mailer client" in new WithApplication() { - app.injector.instanceOf[JMailerClient] must beAnInstanceOf[CommonsMailer] + // Deprecated configuration should still works + "provide the Scala mailer client (even with deprecated configuration)" in new WithApplication(applicationWithDeprecatedMailerConfiguration) { + app.injector.instanceOf[JMailerClient] must beAnInstanceOf[SMTPMailer] } "provide the Scala mocked mailer client" in new WithApplication() { app.injector.instanceOf(bind[MailerClient].qualifiedWith("mock")) must beAnInstanceOf[MockMailer] diff --git a/user-manual.adoc b/user-manual.adoc new file mode 100644 index 0000000..70ee5ae --- /dev/null +++ b/user-manual.adoc @@ -0,0 +1,57 @@ += Play mailer plugin User Manual + +== Configure mailer instance + +=== Runtime Dependency Injection + +By default the plugin automatically configure the injected instances with the `application.conf` file: + +```scala +class MyComponent @Inject() (mailer: MailerClient) { + // ... +} +``` + +If you want to configure the injected instances from another source, you will need to override the default provider: + + 1. Create a custom configuration provider: ++ +.CustomSMTPConfigurationProvider.scala +```scala +class CustomSMTPConfigurationProvider extends Provider[SMTPConfiguration] { + override def get() { + // Custom configuration + new SMTPConfiguration("typesafe.org", 1234) + } +} + +class CustomMailerConfigurationModule extends Module { + def bindings(environment: Environment, configuration: Configuration) = Seq( + bind[SMTPConfiguration].toProvider[CustomSMTPConfigurationProvider] + ) +} +``` + + 2. Override the default provider in the `application.conf` file: ++ +.application.conf +```bash +play { + modules { + # Disable the default provider + disabled += "play.api.libs.mailer.SMTPConfigurationModule" + # Enable the custom provider (see above) + enabled += "controllers.CustomMailerConfigurationModule" + } +} +``` + +=== New instances + +You can also use the `SMTPMailer` constructor to create new instances with custom configuration: + +```scala +val email = Email("Simple email", "Mister FROM ") +new SMTPMailer(SMTPConfiguration("typesafe.org", 1234)).send(email) +new SMTPMailer(SMTPConfiguration("playframework.com", 5678)).send(email) +```