Skip to content
This repository
tree: afb026038e
Fetching contributors…

Cannot retrieve contributors at this time

file 288 lines (243 sloc) 10.265 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
/*
* Copyright 2006-2011 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.liftweb
package util

import scala.xml.{NodeSeq}
import javax.mail._
import javax.mail.internet._
import javax.naming.{Context, InitialContext}
import java.util.Properties
import common._
import actor._

/**
* Utilities for sending email.
*/
object Mailer extends MailerImpl

/**
* This trait implmenets the mail sending
*/
protected trait MailerImpl extends SimpleInjector {
  private val logger = Logger(classOf[MailerImpl])
  
  sealed abstract class MailTypes
  /**
* Add message headers to outgoing messages
*/
  final case class MessageHeader(name: String, value: String) extends MailTypes
  sealed abstract class MailBodyType extends MailTypes
  final case class PlusImageHolder(name: String, mimeType: String, bytes: Array[Byte])

  /**
* Represents a text/plain mail body. The given text will
* be encoded as UTF-8 when sent.
*/
  final case class PlainMailBodyType(text: String) extends MailBodyType

  /**
* Represents a text/plain mail body that is encoded with the
* specified charset
*/
  final case class PlainPlusBodyType(text: String, charset: String) extends MailBodyType

  final case class XHTMLMailBodyType(text: NodeSeq) extends MailBodyType
  final case class XHTMLPlusImages(text: NodeSeq, items: PlusImageHolder*) extends MailBodyType

  sealed abstract class RoutingType extends MailTypes
  sealed abstract class AddressType(val adr: String) extends RoutingType
  final case class From(address: String) extends AddressType(address)
  final case class To(address: String) extends AddressType(address)
  final case class CC(address: String) extends AddressType(address)
  final case class Subject(subject: String) extends RoutingType
  final case class BCC(address: String) extends AddressType(address)
  final case class ReplyTo(address: String) extends AddressType(address)

  implicit def xmlToMailBodyType(html: NodeSeq): MailBodyType = XHTMLMailBodyType(html)

  final case class MessageInfo(from: From, subject: Subject, info: List[MailTypes])

  implicit def addressToAddress(in: AddressType): Address = new InternetAddress(in.adr)

  implicit def adListToAdArray(in: List[AddressType]): Array[Address] = in.map(a => new InternetAddress(a.adr)).toArray

  /**
* Passwords cannot be accessed via System.getProperty. Instead, we
* provide a means of explicitlysetting the authenticator.
*/
  //def authenticator = authenticatorFunc
  var authenticator: Box[Authenticator] = Empty

  /**
* Use the mailer resource in your container by specifying the JNDI name
*/
  var jndiName: Box[String] = Empty

  /**
* Custom properties for the JNDI session
*/
  var customProperties: Map[String, String] = Map()

  lazy val jndiSession: Box[Session] =
  for{
    name <- jndiName
    contextObj <- Helpers.tryo(new InitialContext().lookup("java:comp/env"))
    context <- Box.asA[Context](contextObj)
    sessionObj <- Helpers.tryo(context.lookup(name))
    session <- Box.asA[Session](sessionObj)
  } yield session

  lazy val properties: Properties = {
    val p = System.getProperties.clone.asInstanceOf[Properties]
    customProperties.foreach {case (name, value) => p.put(name, value)}
    // allow the properties file to set/override system properties

    Props.props.foreach {
      case (name, value) =>
        p.setProperty(name, value)
    }
    p
  }

  /**
* The host that should be used to send mail.
*/
  def host = hostFunc()

  /**
* To change the way the host is calculated, set this to the function that calcualtes the host name.
* By default: System.getProperty("mail.smtp.host")
*/
  var hostFunc: () => String = _host _

  private def _host = properties.getProperty("mail.smtp.host") match {
    case null => "localhost"
    case s => s
  }

  def buildProps: Properties = {
    val p = properties.clone.asInstanceOf[Properties]
    p.getProperty("mail.smtp.host") match {
      case null => p.put("mail.smtp.host", host)
      case _ =>
    }

    p
  }

  /**
* Set the mail.charset property to something other than UTF-8 for non-UTF-8
* mail.
*/
  lazy val charSet = properties.getProperty("mail.charset") match {
    case null => "UTF-8"
    case x => x
  }

  // def host_=(hostname: String) = System.setProperty("mail.smtp.host", hostname)

  protected class MsgSender extends SpecializedLiftActor[MessageInfo] {
    protected def messageHandler = {
      case MessageInfo(from, subject, info) =>
        try {
          msgSendImpl(from, subject, info)
        } catch {
          case e: Exception => logger.error("Couldn't send mail", e)
        }
    }
  }

  protected def performTransportSend(msg: MimeMessage) = {
    import Props.RunModes._
    (Props.mode match {
        case Development => devModeSend.vend
        case Test => testModeSend.vend
        case Staging => stagingModeSend.vend
        case Production => productionModeSend.vend
        case Pilot => pilotModeSend.vend
        case Profile => profileModeSend.vend
      }).apply(msg)
  }

  /**
* How to send a message in dev mode. By default, use Transport.send(msg)
*/
  lazy val devModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {}

  /**
* How to send a message in test mode. By default, log the message
*/
  lazy val testModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => logger.info("Sending Mime Message: "+m)) {}

  /**
* How to send a message in staging mode. By default, use Transport.send(msg)
*/
  lazy val stagingModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {}

  /**
* How to send a message in production mode. By default, use Transport.send(msg)
*/
  lazy val productionModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {}

  /**
* How to send a message in pilot mode. By default, use Transport.send(msg)
*/
  lazy val pilotModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {}

  /**
* How to send a message in profile mode. By default, use Transport.send(msg)
*/
  lazy val profileModeSend: Inject[MimeMessage => Unit] = new Inject[MimeMessage => Unit]((m: MimeMessage) => Transport.send(m)) {}

  /**
* Synchronously send an email.
*/
  def blockingSendMail(from: From, subject: Subject, rest: MailTypes*) {
    msgSendImpl(from, subject, rest.toList)
  }
  
  def msgSendImpl(from: From, subject: Subject, info: List[MailTypes]) {
    val session = authenticator match {
      case Full(a) => jndiSession openOr Session.getInstance(buildProps, a)
      case _ => jndiSession openOr Session.getInstance(buildProps)
    }

    val message = new MimeMessage(session)
    message.setFrom(from)
    message.setRecipients(Message.RecipientType.TO, info.flatMap {case x: To => Some[To](x) case _ => None})
    message.setRecipients(Message.RecipientType.CC, info.flatMap {case x: CC => Some[CC](x) case _ => None})
    message.setRecipients(Message.RecipientType.BCC, info.flatMap {case x: BCC => Some[BCC](x) case _ => None})
    // message.setReplyTo(filter[MailTypes, ReplyTo](info, {case x @ ReplyTo(_) => Some(x); case _ => None}))
    message.setReplyTo(info.flatMap {case x: ReplyTo => Some[ReplyTo](x) case _ => None})
    message.setSubject(subject.subject)
    info.foreach {
      case MessageHeader(name, value) => message.addHeader(name, value)
      case _ =>
    }

    val bodyTypes = info.flatMap {case x: MailBodyType => Some[MailBodyType](x); case _ => None}
    bodyTypes match {
      case PlainMailBodyType(txt) :: Nil =>
        message.setText(txt)

      case _ =>
        val multiPart = new MimeMultipart("alternative")
        bodyTypes.foreach {
          tab =>
          val bp = new MimeBodyPart
          tab match {
            case PlainMailBodyType(txt) => bp.setText(txt, "UTF-8")
            case PlainPlusBodyType(txt, charset) => bp.setText(txt, charset)
            case XHTMLMailBodyType(html) => bp.setContent(html.toString, "text/html; charset=" + charSet)
            case XHTMLPlusImages(html, img@_*) =>
              val html_mp = new MimeMultipart("related")
              val bp2 = new MimeBodyPart
              bp2.setContent(html.toString, "text/html; charset=" + charSet)
              html_mp.addBodyPart(bp2)
              img.foreach {
                i =>
                val rel_bpi = new MimeBodyPart
                rel_bpi.setFileName(i.name)
                rel_bpi.setContentID(i.name)
                rel_bpi.setDisposition("inline")
                rel_bpi.setDataHandler(new javax.activation.DataHandler(new javax.activation.DataSource {
                      def getContentType = i.mimeType

                      def getInputStream = new java.io.ByteArrayInputStream(i.bytes)

                      def getName = i.name

                      def getOutputStream = throw new java.io.IOException("Unable to write to item")
                    }))
                html_mp.addBodyPart(rel_bpi)
              }
              bp.setContent(html_mp)
          }
          multiPart.addBodyPart(bp)
        }
        message.setContent(multiPart);
    }

    MailerImpl.this.performTransportSend(message)
  }

  protected lazy val msgSender = new MsgSender
  
  /**
* Asynchronously send an email.
*/
  def sendMail(from: From, subject: Subject, rest: MailTypes*) {
    // forward it to an actor so there's no time on this thread spent sending the message
    msgSender ! MessageInfo(from, subject, rest.toList)
  }
}
Something went wrong with that request. Please try again.