Email.java
package fr.sii.ogham.email.message;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import fr.sii.ogham.core.message.Message;
import fr.sii.ogham.core.message.capability.HasContentFluent;
import fr.sii.ogham.core.message.capability.HasRecipients;
import fr.sii.ogham.core.message.capability.HasRecipientsFluent;
import fr.sii.ogham.core.message.capability.HasSubject;
import fr.sii.ogham.core.message.capability.HasSubjectFluent;
import fr.sii.ogham.core.message.capability.HasToFluent;
import fr.sii.ogham.core.message.content.Content;
import fr.sii.ogham.core.message.content.MultiContent;
import fr.sii.ogham.core.message.content.StringContent;
import fr.sii.ogham.core.message.fluent.SingleContentBuilder;
import fr.sii.ogham.core.util.Loggable;
import fr.sii.ogham.core.util.EqualsBuilder;
import fr.sii.ogham.core.util.HashCodeBuilder;
import fr.sii.ogham.core.util.StringUtils;
import fr.sii.ogham.email.attachment.Attachment;
import fr.sii.ogham.email.attachment.ContentDisposition;
import fr.sii.ogham.email.message.fluent.AttachBuilder;
import fr.sii.ogham.email.message.fluent.BodyBuilder;
import fr.sii.ogham.email.message.fluent.EmbedBuilder;
/**
* Email message that contains the following information:
* <ul>
* <li>The subject of the mail</li>
* <li>The body of the mail (see {@link Content} and sub classes for more
* information)</li>
* <li>The sender address</li>
* <li>The list of recipient addresses with the type (to, cc, bcc)</li>
* <li>The list of attachments to join to the mail</li>
* </ul>
*
* @author Aurélien Baudet
*
*/
public class Email implements Message, HasContentFluent<Email>, HasSubject, HasSubjectFluent<Email>, HasRecipients<Recipient>, HasRecipientsFluent<Email, Recipient>, HasToFluent<Email>, Loggable {
/**
* The subject
*/
private String subject;
/**
* The email body content
*/
private Content content;
/**
* The sender address
*/
private EmailAddress from;
/**
* The list of recipient addresses with the associated type (to, cc, bcc)
*/
private List<Recipient> recipients;
/**
* The list of attachments to send with the email
*/
private List<Attachment> attachments;
private final SingleContentBuilder<Email> htmlBuilder;
private final SingleContentBuilder<Email> textBuilder;
private final BodyBuilder bodyBuilder;
private final AttachBuilder attachBuilder;
private final EmbedBuilder embedBuilder;
/**
* Instantiates an empty email
*/
public Email() {
super();
recipients = new ArrayList<>();
attachments = new ArrayList<>();
htmlBuilder = new SingleContentBuilder<>(this);
textBuilder = new SingleContentBuilder<>(this);
bodyBuilder = new BodyBuilder(this);
attachBuilder = new AttachBuilder(this);
embedBuilder = new EmbedBuilder(this);
}
// ----------------------- Getter/Setters -----------------------//
@Override
public Content getContent() {
if (content != null) {
return content;
}
return buildContent();
}
@Override
public void setContent(Content content) {
this.content = content;
}
/**
* Get the sender address
*
* @return the sender address
*/
public EmailAddress getFrom() {
return from;
}
/**
* Set the sender address.
*
* @param from
* the sender address
*/
public void setFrom(EmailAddress from) {
this.from = from;
}
/**
* Set the sender address as string (typical address syntax is of the form
* "user@host.domain" or "Personal Name <user@host.domain>").
*
* @param from
* the sender address string (typical address syntax is of the
* form "user@host.domain" or "Personal Name
* <user@host.domain>").
*/
public void setFrom(String from) {
setFrom(new EmailAddress(from));
}
/**
* Get the list of recipients of the mail.
*
* @return the list of recipients
*/
@Override
public List<Recipient> getRecipients() {
return recipients;
}
/**
* Set the whole list of recipients.
*
* @param recipients
* the list of recipients
*/
@Override
public void setRecipients(List<Recipient> recipients) {
this.recipients = recipients;
}
/**
* Get the subject of the mail.
*
* @return the subject of the mail
*/
public String getSubject() {
return subject;
}
/**
* Set the subject of the mail.
*
* @param subject
* the subject of the mail
*/
public void setSubject(String subject) {
this.subject = subject;
}
/**
* Get the list of attachments.
*
* @return the list of attachments
*/
public List<Attachment> getAttachments() {
List<Attachment> merged = new ArrayList<>();
// NOTE: normally it can't be null but EqualsVerifier uses reflection to
// set it to null
if (attachments != null) {
merged.addAll(attachments);
}
// NOTE: normally it can't be null but EqualsVerifier uses reflection to
// set it to null
if (attachBuilder != null) {
merged.addAll(attachBuilder.build());
}
// NOTE: normally it can't be null but EqualsVerifier uses reflection to
// set it to null
if (embedBuilder != null) {
merged.addAll(embedBuilder.build());
}
return merged;
}
/**
* Set the whole list of attachments.
*
* @param attachments
* the list of attachments
*/
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
// ----------------------- Fluent API -----------------------//
/**
* Set the subject of the mail.
*
* @param subject
* the subject of the mail
* @return this instance for fluent chaining
*/
public Email subject(String subject) {
setSubject(subject);
return this;
}
/**
* Set the content (body) of the message.
*
* <p>
* You can use this method to explicitly set a particular {@link Content}
* instance. For example:
*
* <pre>
* {@code
* .content(new TemplateContent("path/to/template", obj));
* }
* </pre>
*
* <p>
* If you prefer, you can instead use the fluent API to set the email
* content (body):
*
* <pre>
* {@code
* .body().template("path/to/template", obj)
* }
* </pre>
*
* @param content
* the content of the message
* @return this instance for fluent chaining
* @see #body()
* @see #html()
* @see #text()
*/
public Email content(Content content) {
setContent(content);
return this;
}
/**
* Set the content (body) of the message. This is a shortcut to
*
* <pre>
* {@code .content(new StringContent(content))}
* </pre>
*
* <p>
* If you prefer, you can instead use the fluent API to set the email
* content (body):
*
* <pre>
* {@code
* .body().string(content)
* }
* </pre>
*
*
* @param content
* the content of the message
* @return this instance for fluent chaining
* @see #body()
* @see #html()
* @see #text()
*/
public Email content(String content) {
return content(new StringContent(content));
}
/**
* Set the content (body) of the message (HTML part).
*
* <p>
* You can use this method in addition to {@link #text()} to provide both a
* main body and an alternative textual body (that is used when HTML format
* is not supported by the email client).
*
* <p>
* This method provides fluent chaining to guide developer. It has the same
* effect has using {@link #content(Content)}.
*
* <p>
* If you also call either {@link #content(Content)},
* {@link #content(String)} or {@link #setContent(Content)} then this method
* has no effect.
*
* @return the builder for building HTML part
* @since 3.0.0
*/
public SingleContentBuilder<Email> html() {
return htmlBuilder;
}
/**
* Set the content (body) of the message (text part).
*
* <p>
* You can use this method in addition to {@link #html()} to provide both a
* main body and an alternative textual body (that is used when HTML format
* is not supported by the email client). If you only call {@link #text()},
* then the textual content is used as the main body.
*
* <p>
* This method provides fluent chaining to guide developer. It has the same
* effect has using {@link #content(Content)}.
*
* <p>
* If you also call either {@link #content(Content)},
* {@link #content(String)} or {@link #setContent(Content)} then this method
* has no effect.
*
* @return the builder for building text part
* @since 3.0.0
*/
public SingleContentBuilder<Email> text() {
return textBuilder;
}
/**
* Set the content (body) of the message.
*
* <p>
* This is the method that you can use in main circumstances to set the
* body:
* <ul>
* <li>When you want to set a single textual body (no alternative):
*
* <pre>
* {@code .body().string("text")}
* </pre>
*
* </li>
* <li>When you want to set a single HTML body (no alternative):
*
* <pre>
* {@code .body().string("<html><body>Hello world</body></html>")}
* </pre>
*
* </li>
* <li>When you want to set a single text body (no alternative) based on a
* template (extension depends on template parser):
*
* <pre>
* {@code .body().template("path/to/text/template.txt", obj)}
* </pre>
*
* </li>
* <li>When you want to set a single HTML body (no alternative) based on a
* template (extension depends on template parser):
*
* <pre>
* {@code .body().template("path/to/text/template.html", obj)}
* </pre>
*
* </li>
* <li>When you want to set both HTML and textual alternative based on two
* different templates (same path but without extension):
*
* <pre>
* {@code .body().template("path/to/text/template", obj)}
* </pre>
*
* </li>
* </ul>
*
* <p>
* This method provides fluent chaining to guide developer. It has the same
* effect has using {@link #content(Content)}.
*
* <p>
* If you also call either {@link #content(Content)},
* {@link #content(String)}, {@link #setContent(Content)}, {@link #html()}
* or {@link #text()} then this method has no effect because they are more
* specific.
*
* @return the builder for building the body
* @since 3.0.0
*/
public BodyBuilder body() {
return bodyBuilder;
}
/**
* Attach a file to the email. The attachment must have a name.
*
* <p>
* The file is attached with the {@link ContentDisposition#ATTACHMENT}
* disposition.
*
* <p>
* This method provides fluent chaining to guide the developer. This method
* has the same effect has using {@link #attach(Attachment)},
* {@link #attach(Attachment...)} or {@link #attach(List)}.
*
* @return the builder for building the attachments
* @since 3.0.0
*/
public AttachBuilder attach() {
return attachBuilder;
}
/**
* Embed a file in the email. This is mainly used for images. The embedded
* file must be referenced in the body of the email using a
* <a href="https://tools.ietf.org/html/rfc4021#section-2.2.2">Content-ID
* (or CID)</a>.
*
* <p>
* The file is attached {@link ContentDisposition#INLINE} disposition.
*
* <p>
* This method provides fluent chaining to guide the developer. This method
* has the same effect has using {@link #attach(Attachment)},
* {@link #attach(Attachment...)} or {@link #attach(List)}.
*
* @return the builder for building the attachments
* @since 3.0.0
*/
public EmbedBuilder embed() {
return embedBuilder;
}
/**
* Set the sender address. It supports both "user@domain.host" and "Personal
* Name <user@host.domain>" formats.
*
* @param from
* the sender address
* @return this instance for fluent chaining
*/
public Email from(EmailAddress from) {
setFrom(from);
return this;
}
/**
* Set the sender address as string (typical address syntax is of the form
* "user@host.domain" or "Personal Name <user@host.domain>").
*
* @param from
* the sender address string (typical address syntax is of the
* form "user@host.domain" or "Personal Name
* <user@host.domain>").
* @return this instance for fluent chaining
*/
public Email from(String from) {
setFrom(from);
return this;
}
/**
* Set the whole list of recipients.
*
* @param recipients
* the list of recipients
* @return this instance for fluent chaining
*/
@Override
public Email recipients(List<Recipient> recipients) {
setRecipients(recipients);
return this;
}
/**
* Set the whole list of attachments.
*
* @param attachments
* the list of attachments
* @return this instance for fluent chaining
*/
public Email attach(List<Attachment> attachments) {
setAttachments(attachments);
return this;
}
/**
* Set the whole list of attachments.
*
* @param attachments
* the list of attachments
* @return this instance for fluent chaining
*/
public Email attach(Attachment... attachments) {
for (Attachment attachment : attachments) {
attach(attachment);
}
return this;
}
/**
* Add an attachment to join to the mail.
*
* @param attachment
* the attachment to add
* @return this instance for fluent chaining
*/
public Email attach(Attachment attachment) {
attachments.add(attachment);
return this;
}
/**
* Add a recipient of the mail.
*
* @param recipients
* one or several recipient to add
* @return this instance for fluent chaining
*/
@Override
public Email recipient(Recipient... recipients) {
this.recipients.addAll(Arrays.asList(recipients));
return this;
}
/**
* Add a "to" recipient address. It supports both "user@domain.host" and
* "Personal Name <user@host.domain>" formats.
*
* @param to
* one or several recipient addresses
* @return this instance for fluent chaining
*/
public Email to(String... to) {
for (String t : to) {
to(new EmailAddress(t));
}
return this;
}
/**
* Add a "to" recipient address.
*
* @param to
* one or several recipient addresses
* @return this instance for fluent chaining
*/
public Email to(EmailAddress... to) {
for (EmailAddress t : to) {
recipient(t, RecipientType.TO);
}
return this;
}
/**
* Add a "cc" recipient address. It supports both "user@domain.host" and
* "Personal Name <user@host.domain>" formats.
*
* @param cc
* one or several recipient addresses
* @return this instance for fluent chaining
*/
public Email cc(String... cc) {
for (String c : cc) {
cc(new EmailAddress(c));
}
return this;
}
/**
* Add a "cc" recipient address.
*
* @param cc
* one or several recipient addresses
* @return this instance for fluent chaining
*/
public Email cc(EmailAddress... cc) {
for (EmailAddress c : cc) {
recipient(c, RecipientType.CC);
}
return this;
}
/**
* Add a "bcc" recipient address. It supports both "user@domain.host" and
* "Personal Name <user@host.domain>" formats.
*
* @param bcc
* one or several recipient addresses
* @return this instance for fluent chaining
*/
public Email bcc(String... bcc) {
for (String b : bcc) {
bcc(new EmailAddress(b));
}
return this;
}
/**
* Add a "bcc" recipient address.
*
* @param bcc
* one or several recipient addresses
* @return this instance for fluent chaining
*/
public Email bcc(EmailAddress... bcc) {
for (EmailAddress b : bcc) {
recipient(b, RecipientType.BCC);
}
return this;
}
/**
* Add a recipient specifying its address and the type (to, cc, bcc).
*
* @param recipient
* the recipient address
* @param type
* the type (to, cc, bcc)
* @return this instance for fluent chaining
*/
public Email recipient(EmailAddress recipient, RecipientType type) {
recipient(new Recipient(recipient, type));
return this;
}
// ----------------------- Utilities -----------------------//
/**
* Converts a list of string to a list of recipients. Each recipient will
* have the type {@link RecipientType#TO}.
*
* @param to
* the list of addresses to convert (typical address syntax is of
* the form "user@host.domain" or "Personal Name
* <user@host.domain>")
* @return the list of recipients
*/
public static Recipient[] toRecipient(List<String> to) {
Recipient[] addresses = new Recipient[to.size()];
int i = 0;
for (String t : to) {
addresses[i] = new Recipient(t);
i++;
}
return addresses;
}
/**
* Converts a list of {@link EmailAddress} to a list of recipients. Each
* recipient will have the type {@link RecipientType#TO}.
*
* @param to
* the list of addresses to convert
* @return the list of recipients
*/
public static Recipient[] toRecipient(EmailAddress[] to) {
Recipient[] addresses = new Recipient[to.length];
int i = 0;
for (EmailAddress t : to) {
addresses[i] = new Recipient(t);
i++;
}
return addresses;
}
/**
* Converts a list of string to a list of recipients. Each recipient will
* have the type {@link RecipientType#TO}.
*
* @param to
* the list of addresses to convert (typical address syntax is of
* the form "user@host.domain" or "Personal Name
* <user@host.domain>")
* @return the list of recipients
*/
public static Recipient[] toRecipient(String[] to) {
Recipient[] addresses = new Recipient[to.length];
int i = 0;
for (String t : to) {
addresses[i] = new Recipient(t);
i++;
}
return addresses;
}
@Override
public String toString() {
return toString(false);
}
@Override
public String toLogString() {
return toString(true);
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(subject, getContent(), from, recipients, getAttachments()).hashCode();
}
@Override
public boolean equals(Object obj) {
return new EqualsBuilder(this, obj).appendFields("subject", "content", "from", "recipients", "attachments").isEqual();
}
private String toString(boolean includeContent) {
StringBuilder builder = new StringBuilder();
builder.append("Email message\r\nFrom: ").append(from);
for (RecipientType type : RecipientType.values()) {
List<EmailAddress> addresses = new ArrayList<>();
for (Recipient recipient : recipients) {
if (type == recipient.getType()) {
addresses.add(recipient.getAddress());
}
}
if (!addresses.isEmpty()) {
builder.append("\r\n");
builder.append(type).append(": ");
builder.append(StringUtils.join(addresses, ", "));
}
}
builder.append("\r\nSubject: ").append(subject);
builder.append("\r\n----------------------------------\r\n").append(includeContent ? getContent() : "<Content skipped>");
if (attachments != null && !attachments.isEmpty()) {
builder.append("\r\n----------------------------------").append("\r\nAttachments: ").append(getAttachments());
}
builder.append("\r\n==================================\r\n");
return builder.toString();
}
private Content buildContent() {
// NOTE: normally it can't be null but EqualsVerifier uses reflection to
// set it to null
Content html = htmlBuilder == null ? null : htmlBuilder.build();
// NOTE: normally it can't be null but EqualsVerifier uses reflection to
// set it to null
Content text = textBuilder == null ? null : textBuilder.build();
if (html != null && text != null) {
return new MultiContent(text, html);
}
if (html != null) {
return html;
}
if (text != null) {
return text;
}
// NOTE: normally it can't be null but EqualsVerifier uses reflection to
// set it to null
Content body = bodyBuilder == null ? null : bodyBuilder.build();
if (body != null) {
return body;
}
return null;
}
}