Skip to content
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
32 changes: 26 additions & 6 deletions obp-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,6 @@
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-email -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
Expand Down Expand Up @@ -408,6 +402,12 @@
<groupId>org.asynchttpclient</groupId>
<artifactId>async-http-client</artifactId>
<version>2.10.4</version>
<exclusions>
<exclusion>
<artifactId>javax.activation</artifactId>
<groupId>com.sun.activation</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- grpc related end-->

Expand All @@ -416,6 +416,16 @@
<groupId>org.scalikejdbc</groupId>
<artifactId>scalikejdbc_${scala.version}</artifactId>
<version>3.4.0</version>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
</exclusion>
<exclusion>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
Expand Down Expand Up @@ -509,6 +519,16 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
Expand Down
221 changes: 101 additions & 120 deletions obp-api/src/main/scala/code/api/util/CommonsEmailWrapper.scala
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package code.api.util

import code.util.Helper.MdcLoggable
import jakarta.activation.{DataHandler, FileDataSource, URLDataSource}
import jakarta.mail._
import jakarta.mail.internet._
import net.liftweb.common.{Box, Empty, Full}
import org.apache.commons.mail._

import java.io.File
import java.net.URL
import java.util.Properties

/**
* Apache Commons Email Wrapper for OBP-API
* This wrapper provides a simple interface to send emails using Apache Commons Email
* instead of Lift Web's Mailer
* Jakarta Mail Wrapper for OBP-API
* This wrapper provides a simple interface to send emails using Jakarta Mail
*/
object CommonsEmailWrapper extends MdcLoggable {

/**
* Email configuration case class
*/
case class EmailConfig(
smtpHost: String,
smtpPort: Int,
Expand All @@ -24,12 +24,9 @@ object CommonsEmailWrapper extends MdcLoggable {
useTLS: Boolean = true,
useSSL: Boolean = false,
debug: Boolean = false,
tlsProtocols: String = "TLSv1.2" // TLS protocols to use
tlsProtocols: String = "TLSv1.2"
)

/**
* Email content case class
*/
case class EmailContent(
from: String,
to: List[String],
Expand All @@ -41,9 +38,12 @@ object CommonsEmailWrapper extends MdcLoggable {
attachments: List[EmailAttachment] = List.empty
)

/**
* Get default email configuration from OBP-API properties
*/
case class EmailAttachment(
filePath: Option[String] = None,
url: Option[String] = None,
name: Option[String] = None
)

def getDefaultEmailConfig(): EmailConfig = {
EmailConfig(
smtpHost = APIUtil.getPropsValue("mail.smtp.host", "localhost"),
Expand All @@ -57,156 +57,137 @@ object CommonsEmailWrapper extends MdcLoggable {
)
}

/**
* Send simple text email with default configuration
*/
def sendTextEmail(content: EmailContent): Box[String] = {
sendTextEmail(getDefaultEmailConfig(), content)
}

/**
* Send HTML email with default configuration
*/
def sendHtmlEmail(content: EmailContent): Box[String] = {
sendHtmlEmail(getDefaultEmailConfig(), content)
}

/**
* Send email with attachments using default configuration
*/
def sendEmailWithAttachments(content: EmailContent): Box[String] = {
sendEmailWithAttachments(getDefaultEmailConfig(), content)
}

/**
* Send simple text email
*/
def sendTextEmail(config: EmailConfig, content: EmailContent): Box[String] = {
try {
logger.info(s"Sending text email from ${content.from} to ${content.to.mkString(", ")}")

val email = new SimpleEmail()
configureEmail(email, config, content)

// Set text content
content.textContent match {
case Some(text) => email.setMsg(text)
case None => email.setMsg("")
}

val messageId = email.send()
logger.info(s"Email sent successfully with Message-ID: $messageId")
Full(messageId)
logger.debug(s"Sending text email from ${content.from} to ${content.to.mkString(", ")}")
val session = createSession(config)
val message = new MimeMessage(session)
setCommonHeaders(message, content)
message.setText(content.textContent.getOrElse(""), "UTF-8")
Transport.send(message)
Full(message.getMessageID)
} catch {
case e: Exception =>
logger.error(s"Failed to send text email: ${e.getMessage}", e)
Empty
}
}

/**
* Send HTML email
*/
def sendHtmlEmail(config: EmailConfig, content: EmailContent): Box[String] = {
try {
logger.info(s"Sending HTML email from ${content.from} to ${content.to.mkString(", ")}")

val email = new HtmlEmail()
configureEmail(email, config, content)

// Set HTML content
content.htmlContent match {
case Some(html) => email.setHtmlMsg(html)
case None => email.setHtmlMsg("<html><body>No content</body></html>")
logger.debug(s"Sending HTML email from ${content.from} to ${content.to.mkString(", ")}")
val session = createSession(config)
val message = new MimeMessage(session)
setCommonHeaders(message, content)
val multipart = {
new MimeMultipart("alternative")
}
content.textContent.foreach { text =>
val textPart = new MimeBodyPart()
textPart.setText(text, "UTF-8")
multipart.addBodyPart(textPart)
}
content.htmlContent.foreach { html =>
val htmlPart = new MimeBodyPart()
htmlPart.setContent(html, "text/html; charset=UTF-8")
multipart.addBodyPart(htmlPart)
}

// Set text content as fallback
content.textContent.foreach(email.setTextMsg)

val messageId = email.send()
logger.info(s"HTML email sent successfully with Message-ID: $messageId")
Full(messageId)
message.setContent(multipart)
Transport.send(message)
Full(message.getMessageID)
} catch {
case e: Exception =>
logger.error(s"Failed to send HTML email: ${e.getMessage}", e)
Empty
}
}

/**
* Send email with attachments
*/
def sendEmailWithAttachments(config: EmailConfig, content: EmailContent): Box[String] = {
try {
logger.info(s"Sending email with attachments from ${content.from} to ${content.to.mkString(", ")}")

val email = new MultiPartEmail()
configureEmail(email, config, content)

// Set text content
content.textContent.foreach(email.setMsg)

logger.debug(s"Sending email with attachments from ${content.from} to ${content.to.mkString(", ")}")
val session = createSession(config)
val message = new MimeMessage(session)
setCommonHeaders(message, content)
val multipart = new MimeMultipart()
// Add text or HTML part
(content.htmlContent, content.textContent) match {
case (Some(html), _) =>
val htmlPart = new MimeBodyPart()
htmlPart.setContent(html, "text/html; charset=UTF-8")
multipart.addBodyPart(htmlPart)
case (None, Some(text)) =>
val textPart = new MimeBodyPart()
textPart.setText(text, "UTF-8")
multipart.addBodyPart(textPart)
case _ =>
val textPart = new MimeBodyPart()
textPart.setText("", "UTF-8")
multipart.addBodyPart(textPart)
}
// Add attachments
content.attachments.foreach(email.attach)

val messageId = email.send()
logger.info(s"Email with attachments sent successfully with Message-ID: $messageId")
Full(messageId)
content.attachments.foreach { att =>
val attachPart = new MimeBodyPart()
if (att.filePath.isDefined) {
val fds = new FileDataSource(new File(att.filePath.get))
attachPart.setDataHandler(new DataHandler(fds))
attachPart.setFileName(att.name.getOrElse(new File(att.filePath.get).getName))
} else if (att.url.isDefined) {
val uds = new URLDataSource(new URL(att.url.get))
attachPart.setDataHandler(new DataHandler(uds))
attachPart.setFileName(att.name.getOrElse(att.url.get.split('/').last))
}
multipart.addBodyPart(attachPart)
}
message.setContent(multipart)
Transport.send(message)
Full(message.getMessageID)
} catch {
case e: Exception =>
logger.error(s"Failed to send email with attachments: ${e.getMessage}", e)
Empty
}
}

/**
* Configure email with common settings
*/
private def configureEmail(email: Email, config: EmailConfig, content: EmailContent): Unit = {
// SMTP Configuration
email.setHostName(config.smtpHost)
email.setSmtpPort(config.smtpPort)
email.setAuthenticator(new DefaultAuthenticator(config.username, config.password))
email.setSSLOnConnect(config.useSSL)
email.setStartTLSEnabled(config.useTLS)
email.setDebug(config.debug)
email.getMailSession.getProperties.setProperty("mail.smtp.ssl.protocols", config.tlsProtocols)

// Set charset
email.setCharset("UTF-8")

// Set sender
email.setFrom(content.from)

// Set recipients
content.to.foreach(email.addTo)
content.cc.foreach(email.addCc)
content.bcc.foreach(email.addBcc)

// Set subject
email.setSubject(content.subject)
private def createSession(config: EmailConfig): Session = {
val props = new Properties()
props.put("mail.smtp.host", config.smtpHost)
props.put("mail.smtp.port", config.smtpPort.toString)
props.put("mail.smtp.auth", "true")
props.put("mail.smtp.starttls.enable", config.useTLS.toString)
props.put("mail.smtp.ssl.enable", config.useSSL.toString)
props.put("mail.debug", config.debug.toString)
props.put("mail.smtp.ssl.protocols", config.tlsProtocols)
val authenticator = new Authenticator() {
override def getPasswordAuthentication: PasswordAuthentication =
new PasswordAuthentication(config.username, config.password)
}
Session.getInstance(props, authenticator)
}

/**
* Create email attachment from file
*/
def createFileAttachment(filePath: String, name: Option[String] = None): EmailAttachment = {
val attachment = new EmailAttachment()
attachment.setPath(filePath)
attachment.setDisposition(EmailAttachment.ATTACHMENT)
name.foreach(attachment.setName)
attachment
private def setCommonHeaders(message: MimeMessage, content: EmailContent): Unit = {
message.setFrom(new InternetAddress(content.from))
content.to.foreach(addr => message.addRecipient(Message.RecipientType.TO, new InternetAddress(addr)))
content.cc.foreach(addr => message.addRecipient(Message.RecipientType.CC, new InternetAddress(addr)))
content.bcc.foreach(addr => message.addRecipient(Message.RecipientType.BCC, new InternetAddress(addr)))
message.setSubject(content.subject, "UTF-8")
}

/**
* Create email attachment from URL
*/
def createUrlAttachment(url: String, name: String): EmailAttachment = {
val attachment = new EmailAttachment()
attachment.setURL(new URL(url))
attachment.setDisposition(EmailAttachment.ATTACHMENT)
attachment.setName(name)
attachment
}
def createFileAttachment(filePath: String, name: Option[String] = None): EmailAttachment =
EmailAttachment(filePath = Some(filePath), url = None, name = name)

def createUrlAttachment(url: String, name: String): EmailAttachment =
EmailAttachment(filePath = None, url = Some(url), name = Some(name))

}
Loading