Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immutable mailer instance #63

Merged
merged 2 commits into from
Sep 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -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[]
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -63,11 +67,7 @@ public class MyComponent {
// sends text, HTML or both...
.setBodyText("A text message")
.setBodyHtml("<html><body><p>An <b>html</b> message</p></body></html>");
// 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);
mailerClient.send(email);
}
}
```
Expand All @@ -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",
Expand All @@ -92,27 +96,31 @@ def sendEmail {
bodyText = Some("A text message"),
bodyHtml = Some("<html><body><p>An <b>html</b> message</p></body></html>")
)
// 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

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

Declare the `MailerClient` without the `@Inject` annotation:

```scala
import play.api.libs.mailer._

class MyComponent(mailerClient: MailerClient)
```

Expand Down
15 changes: 0 additions & 15 deletions samples/runtimeDI/app/controllers/ApplicationJava.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {

Expand All @@ -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("[email protected]")
.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: 3 additions & 4 deletions samples/runtimeDI/app/controllers/ApplicationScala.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -27,9 +26,9 @@ class ApplicationScala @Inject()(mailer: MailerClient) extends Controller {
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)
def sendWithCustomMailer = Action {
val mailer = new SMTPMailer(SMTPConfiguration("typesafe.org", 1234))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I think it adds a lot of flexibility.

val id = mailer.send(Email("Simple email", "Mister FROM <from@email.com>"))
Ok(s"Email $id sent!")
}
}
Original file line number Diff line number Diff line change
@@ -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]
)
}
6 changes: 6 additions & 0 deletions samples/runtimeDI/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ logger.application=DEBUG
# debug=false
# mock=false
#}
#play {
# modules {
# disabled += "play.api.libs.mailer.SMTPConfigurationModule"
# enabled += "controllers.CustomMailerConfigurationModule"
# }
#}
7 changes: 3 additions & 4 deletions samples/runtimeDI/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -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()
8 changes: 0 additions & 8 deletions src/main/java/play/libs/mailer/MailerClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions src/main/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
play {
modules {
enabled += "play.api.libs.mailer.MailerModule"
enabled += "play.api.libs.mailer.SMTPConfigurationModule"
}
mailer {
host = null
Expand Down
90 changes: 46 additions & 44 deletions src/main/scala/play/api/libs/mailer/MailerPlugin.scala
Original file line number Diff line number Diff line change
@@ -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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perfect merge 👍

}

// 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")
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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

Expand All @@ -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) }
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -288,23 +266,47 @@ 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"),
config.getOptional[String]("user"),
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]
)
}
Loading