blob: 57a7b507c40efbf86daf70da49020ade37232b64 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.sling.commons.messaging.mail.internal;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import org.apache.sling.commons.messaging.mail.MessageBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SimpleMessageBuilder implements MessageBuilder {
private final Session session;
private InternetHeaders headers = new InternetHeaders();
private InternetAddress from;
private List<InternetAddress> toRecipients = new LinkedList<>();
private List<InternetAddress> ccRecipients = new LinkedList<>();
private List<InternetAddress> bccRecipients = new LinkedList<>();
private List<InternetAddress> replyTos = new LinkedList<>();
private String subject;
private String text;
private String html;
private List<Attachment> attachments = new LinkedList<>();
private List<Inline> inlines = new LinkedList<>();
private static final String CONTENT_TYPE_TEXT_HTML = "text/html; charset=utf-8";
private static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain; charset=utf-8";
private static final String CHARSET_UTF8 = "utf-8";
private static final String MULTIPART_SUBTYPE_MIXED = "mixed";
private static final String MULTIPART_SUBTYPE_ALTERNATIVE = "alternative";
private static final String MULTIPART_SUBTYPE_RELATED = "related";
SimpleMessageBuilder(@NotNull final Session session) {
this.session = session;
}
@Override
public @NotNull MessageBuilder header(@NotNull final String name, @Nullable final String value) {
headers.setHeader(name, value);
return this;
}
@Override
public @NotNull MessageBuilder headers(@NotNull final InternetHeaders headers) {
while (headers.getAllHeaders().hasMoreElements()) {
final Header header = headers.getAllHeaders().nextElement();
this.headers.setHeader(header.getName(), header.getValue());
}
return this;
}
@Override
public @NotNull MessageBuilder from(@NotNull final InternetAddress from) {
this.from = from;
return this;
}
@Override
public @NotNull MessageBuilder from(@NotNull final String address) throws AddressException {
final InternetAddress from = new InternetAddress(address);
return from(from);
}
@Override
public @NotNull MessageBuilder from(@NotNull final String address, @NotNull final String name) throws AddressException {
final InternetAddress from = new InternetAddress(address);
try {
from.setPersonal(name, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
//
}
return from(from);
}
@Override
public @NotNull MessageBuilder to(@NotNull final InternetAddress to) {
toRecipients.add(to);
return this;
}
@Override
public @NotNull MessageBuilder to(@NotNull final String address) throws AddressException {
final InternetAddress to = new InternetAddress(address);
return to(to);
}
@Override
public @NotNull MessageBuilder to(@NotNull final String address, @NotNull final String name) throws AddressException {
final InternetAddress to = new InternetAddress(address);
try {
to.setPersonal(name, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
//
}
return to(to);
}
@Override
public @NotNull MessageBuilder cc(@NotNull final InternetAddress cc) {
ccRecipients.add(cc);
return this;
}
@Override
public @NotNull MessageBuilder cc(@NotNull final String address) throws AddressException {
final InternetAddress cc = new InternetAddress(address);
return cc(cc);
}
@Override
public @NotNull MessageBuilder cc(@NotNull final String address, @NotNull final String name) throws AddressException {
final InternetAddress cc = new InternetAddress(address);
try {
cc.setPersonal(name, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
//
}
return cc(cc);
}
@Override
public @NotNull MessageBuilder bcc(@NotNull final InternetAddress bcc) {
bccRecipients.add(bcc);
return this;
}
@Override
public @NotNull MessageBuilder bcc(@NotNull final String address) throws AddressException {
final InternetAddress bcc = new InternetAddress(address);
return bcc(bcc);
}
public @NotNull MessageBuilder bcc(@NotNull final String address, final String name) throws AddressException {
final InternetAddress bcc = new InternetAddress(address);
try {
bcc.setPersonal(name, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
//
}
return bcc(bcc);
}
@Override
public @NotNull MessageBuilder replyTo(@NotNull final InternetAddress replyTo) {
replyTos.add(replyTo);
return this;
}
@Override
public @NotNull MessageBuilder replyTo(@NotNull final String address) throws AddressException {
final InternetAddress replyTo = new InternetAddress(address);
return replyTo(replyTo);
}
@Override
public @NotNull MessageBuilder replyTo(@NotNull final String address, @NotNull final String name) throws AddressException {
final InternetAddress replyTo = new InternetAddress(address);
try {
replyTo.setPersonal(name, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
//
}
return replyTo(replyTo);
}
@Override
public @NotNull MessageBuilder subject(@NotNull final String subject) {
this.subject = subject;
return this;
}
@Override
public @NotNull MessageBuilder text(@NotNull final String text) {
this.text = text;
return this;
}
@Override
public @NotNull MessageBuilder html(@NotNull final String html) {
this.html = html;
return this;
}
@Override
public @NotNull MessageBuilder attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename) {
return attachment(content, type, filename, null);
}
@Override
public @NotNull MessageBuilder attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename, @Nullable Header[] headers) {
final Attachment attachment = new Attachment(content, type, filename, null);
this.attachments.add(attachment);
return this;
}
@Override
public @NotNull MessageBuilder inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid) {
return inline(content, type, cid, null);
}
@Override
public @NotNull MessageBuilder inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid, @Nullable Header[] headers) {
final Inline inline = new Inline(content, type, cid, headers);
this.inlines.add(inline);
return this;
}
private InternetHeaders headers() {
return headers;
}
private InternetAddress from() {
return from;
}
private List<InternetAddress> to() {
return toRecipients;
}
private List<InternetAddress> cc() {
return ccRecipients;
}
private List<InternetAddress> bcc() {
return bccRecipients;
}
private List<InternetAddress> replyTo() {
return replyTos;
}
private String subject() {
return subject;
}
private String text() {
return text;
}
private String html() {
return html;
}
private List<Attachment> attachments() {
return attachments;
}
private List<Inline> inlines() {
return inlines;
}
private boolean hasText() {
return text() != null;
}
private boolean hasHtml() {
return html() != null;
}
private boolean hasAttachments() {
return !attachments().isEmpty();
}
private boolean hasInlines() {
return !inlines().isEmpty();
}
public @NotNull MimeMessage build() throws MessagingException {
final MimeMessage message = new MimeMessage(session);
while (headers().getAllHeaders().hasMoreElements()) {
final Header header = headers.getAllHeaders().nextElement();
message.setHeader(header.getName(), header.getValue());
}
message.setFrom(from());
message.setRecipients(Message.RecipientType.TO, to().toArray(new Address[0]));
message.setRecipients(Message.RecipientType.CC, cc().toArray(new Address[0]));
message.setRecipients(Message.RecipientType.BCC, bcc().toArray(new Address[0]));
message.setReplyTo(replyTos.toArray(new Address[0]));
message.setSubject(subject(), StandardCharsets.UTF_8.name());
if (hasHtml() || hasAttachments() || hasInlines()) {
final MimeMultipart content = new MimeMultipart(MULTIPART_SUBTYPE_MIXED);
if (hasText() && hasHtml()) { // text and html
final MimeMultipart alternative = new MimeMultipart(MULTIPART_SUBTYPE_ALTERNATIVE);
handleHtmlAndInlines(alternative, html(), inlines());
addText(alternative, text());
final MimeBodyPart part = new MimeBodyPart();
part.setContent(alternative);
content.addBodyPart(part);
} else if (hasHtml()) { // html only
handleHtmlAndInlines(content, html(), inlines());
} else { // text only
addText(content, text());
}
addAttachments(content, attachments);
message.setContent(content);
} else {
message.setText(text(), CHARSET_UTF8);
}
return message;
}
private static void handleHtmlAndInlines(final MimeMultipart parent, final String html, final List<Inline> inlines) throws MessagingException {
if (!inlines.isEmpty()) { // html and inlines
final MimeMultipart related = new MimeMultipart(MULTIPART_SUBTYPE_RELATED);
addHtml(related, html);
addInlines(related, inlines);
final MimeBodyPart part = new MimeBodyPart();
part.setContent(related);
parent.addBodyPart(part);
} else { // html
addHtml(parent, html);
}
}
private static void addText(final MimeMultipart parent, final String text) throws MessagingException {
final MimeBodyPart part = new MimeBodyPart();
part.setContent(text, CONTENT_TYPE_TEXT_PLAIN);
parent.addBodyPart(part);
}
private static void addHtml(final MimeMultipart parent, final String html) throws MessagingException {
final MimeBodyPart part = new MimeBodyPart();
part.setContent(html, CONTENT_TYPE_TEXT_HTML);
parent.addBodyPart(part);
}
private static void addAttachments(final MimeMultipart parent, final List<Attachment> attachments) throws MessagingException {
for (final Attachment attachment : attachments) {
try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(attachment.content)) {
final MimeBodyPart part = new MimeBodyPart();
part.setDisposition(Part.ATTACHMENT);
part.setFileName(attachment.filename);
setDataHandler(part, inputStream, attachment.type);
if (attachment.headers != null) {
setHeaders(part, attachment.headers);
}
parent.addBodyPart(part);
} catch (Exception e) {
final String message = String.format("Adding attachment failed: %s", attachment.filename);
throw new MessagingException(message, e);
}
}
}
private static void addInlines(final MimeMultipart parent, final List<Inline> inlines) throws MessagingException {
for (final Inline inline : inlines) {
try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(inline.content)) {
final MimeBodyPart part = new MimeBodyPart();
part.setDisposition(Part.INLINE);
part.setContentID(String.format("<%s>", inline.cid));
setDataHandler(part, inputStream, inline.type);
if (inline.headers != null) {
setHeaders(part, inline.headers);
}
parent.addBodyPart(part);
} catch (Exception e) {
final String message = String.format("Adding inline object failed: %s", inline.cid);
throw new MessagingException(message, e);
}
}
}
private static void setDataHandler(final MimeBodyPart part, final InputStream inputStream, final String type) throws MessagingException, IOException {
final DataSource source = new ByteArrayDataSource(inputStream, type);
final DataHandler handler = new DataHandler(source);
part.setDataHandler(handler);
}
private static void setHeaders(final MimeBodyPart part, final Header[] headers) throws MessagingException {
for (final Header header : headers) {
part.setHeader(header.getName(), header.getValue());
}
}
private static class Attachment {
final byte[] content;
final String type;
final String filename;
final Header[] headers;
Attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename, @Nullable final Header[] headers) {
this.content = content;
this.type = type;
this.filename = filename;
this.headers = headers;
}
}
private static class Inline {
final byte[] content;
final String type;
final String cid;
final Header[] headers;
Inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid, @Nullable final Header[] headers) {
this.content = content;
this.type = type;
this.cid = cid;
this.headers = headers;
}
}
}