// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.mail;
import jodd.util.StringPool;
import javax.activation.DataHandler;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.MessagingException;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Encapsulates email sending session. Prepares and sends message(s).
*/
public class SendMailSession implements AutoCloseable {
private static final String ALTERNATIVE = "alternative";
private static final String RELATED = "related";
private static final String CHARSET = ";charset=";
private static final String INLINE = "inline";
protected final Session mailSession;
protected final Transport mailTransport;
static {
JoddMail.mailSystem.defineJavaMailSystemProperties();
}
/**
* Creates new mail session.
*/
public SendMailSession(Session session, Transport transport) {
this.mailSession = session;
this.mailTransport = transport;
}
/**
* Opens mail session.
*/
public void open() {
try {
mailTransport.connect();
} catch (MessagingException msex) {
throw new MailException("Failed to connect", msex);
}
}
/**
* Returns {@code true} if mail session is still connected.
*/
public boolean isConnected() {
return mailTransport.isConnected();
}
/**
* Prepares message and sends it.
* Returns Message ID of sent email.
*/
public String sendMail(Email mail) {
MimeMessage msg;
try {
msg = createMessage(mail, mailSession);
} catch (MessagingException mex) {
throw new MailException("Failed to prepare email: " + mail, mex);
}
try {
mailTransport.sendMessage(msg, msg.getAllRecipients());
return msg.getMessageID();
} catch (MessagingException mex) {
throw new MailException("Failed to send email: " + mail, mex);
}
}
/**
* Closes session.
*/
@Override
public void close() {
try {
mailTransport.close();
} catch (MessagingException mex) {
throw new MailException("Failed to close session", mex);
}
}
// ---------------------------------------------------------------- adapter
/**
* Creates new JavaX message from {@link Email email}.
*/
protected MimeMessage createMessage(Email email, Session session) throws MessagingException {
MimeMessage msg = new MimeMessage(session);
msg.setFrom(email.getFrom().toInternetAddress());
// to
int totalTo = email.getTo().length;
InternetAddress[] address = new InternetAddress[totalTo];
for (int i = 0; i < totalTo; i++) {
address[i] = email.getTo()[i].toInternetAddress();
}
msg.setRecipients(Message.RecipientType.TO, address);
// replyTo
if (email.getReplyTo() != null) {
int totalReplyTo = email.getReplyTo().length;
address = new InternetAddress[totalReplyTo];
for (int i = 0; i < totalReplyTo; i++) {
address[i] = email.getReplyTo()[i].toInternetAddress();
}
msg.setReplyTo(address);
}
// cc
if (email.getCc() != null) {
int totalCc = email.getCc().length;
address = new InternetAddress[totalCc];
for (int i = 0; i < totalCc; i++) {
address[i] = email.getCc()[i].toInternetAddress();
}
msg.setRecipients(Message.RecipientType.CC, address);
}
// bcc
if (email.getBcc() != null) {
int totalBcc = email.getBcc().length;
address = new InternetAddress[totalBcc];
for (int i = 0; i < totalBcc; i++) {
address[i] = email.getBcc()[i].toInternetAddress();
}
msg.setRecipients(Message.RecipientType.BCC, address);
}
// subject & date
if (email.getSubjectEncoding() != null) {
msg.setSubject(email.getSubject(), email.getSubjectEncoding());
} else {
msg.setSubject(email.getSubject());
}
Date date = email.getSentDate();
if (date == null) {
date = new Date();
}
msg.setSentDate(date);
// headers
Map<String, String> headers = email.getAllHeaders();
if (headers != null) {
for (Map.Entry<String, String> stringStringEntry : headers.entrySet()) {
String value = stringStringEntry.getValue();
msg.setHeader(stringStringEntry.getKey(), value);
}
}
// message data and attachments
final List<EmailMessage> messages = email.getAllMessages();
final List<EmailAttachment> attachments =
email.getAttachments() == null ? null : new ArrayList<>(email.getAttachments());
final int totalMessages = messages.size();
if ((attachments == null) && (totalMessages == 1)) {
// special case: no attachments and just one content
EmailMessage emailMessage = messages.get(0);
msg.setContent(emailMessage.getContent(), emailMessage.getMimeType() + CHARSET + emailMessage.getEncoding());
} else {
Multipart multipart = new MimeMultipart();
Multipart msgMultipart = multipart;
if (totalMessages > 1) {
MimeBodyPart bodyPart = new MimeBodyPart();
msgMultipart = new MimeMultipart(ALTERNATIVE);
bodyPart.setContent(msgMultipart);
multipart.addBodyPart(bodyPart);
}
for (EmailMessage emailMessage : messages) {
// detect embedded attachments
List<EmailAttachment> embeddedAttachments = filterEmbeddedAttachments(attachments, emailMessage);
MimeBodyPart bodyPart = new MimeBodyPart();
if (embeddedAttachments == null) {
// no embedded attachments, just add message
bodyPart.setContent(emailMessage.getContent(), emailMessage.getMimeType() + CHARSET + emailMessage.getEncoding());
}
else {
// embedded attachments detected, join them as related
MimeMultipart relatedMultipart = new MimeMultipart(RELATED);
MimeBodyPart messageData = new MimeBodyPart();
messageData.setContent(emailMessage.getContent(), emailMessage.getMimeType() + CHARSET + emailMessage.getEncoding());
relatedMultipart.addBodyPart(messageData);
for (EmailAttachment att : embeddedAttachments) {
MimeBodyPart attBodyPart = createAttachmentBodyPart(att);
relatedMultipart.addBodyPart(attBodyPart);
}
bodyPart.setContent(relatedMultipart);
}
msgMultipart.addBodyPart(bodyPart);
}
if (attachments != null) {
// attach remaining attachments
for (EmailAttachment att : attachments) {
MimeBodyPart attBodyPart = createAttachmentBodyPart(att);
multipart.addBodyPart(attBodyPart);
}
}
msg.setContent(multipart);
}
return msg;
}
/**
* Creates attachment body part. Handles regular and inline attachments.
*/
protected MimeBodyPart createAttachmentBodyPart(EmailAttachment attachment) throws MessagingException {
MimeBodyPart attBodyPart = new MimeBodyPart();
String attachmentName = attachment.getEncodedName();
if (attachmentName != null) {
attBodyPart.setFileName(attachmentName);
}
attBodyPart.setDataHandler(new DataHandler(attachment.getDataSource()));
if (attachment.getContentId() != null) {
attBodyPart.setContentID(StringPool.LEFT_CHEV + attachment.getContentId() + StringPool.RIGHT_CHEV);
}
if (attachment.isInline()) {
attBodyPart.setDisposition(INLINE);
}
return attBodyPart;
}
/**
* Filters out the list of embedded attachments for given message. If none found, returns <code>null</code>.
*/
protected List<EmailAttachment> filterEmbeddedAttachments(List<EmailAttachment> attachments, EmailMessage emailMessage) {
if (attachments == null) {
return null;
}
List<EmailAttachment> embeddedAttachments = null;
Iterator<EmailAttachment> iterator = attachments.iterator();
while (iterator.hasNext()) {
EmailAttachment emailAttachment = iterator.next();
if (emailAttachment.isEmbeddedInto(emailMessage)) {
if (embeddedAttachments == null) {
embeddedAttachments = new ArrayList<>();
}
embeddedAttachments.add(emailAttachment);
iterator.remove();
}
}
return embeddedAttachments;
}
}