package org.simplejavamail.email;
import org.simplejavamail.internal.util.MiscUtil;
import javax.activation.DataSource;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.mail.Message.RecipientType;
import javax.mail.util.ByteArrayDataSource;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.simplejavamail.internal.util.MiscUtil.extractEmailAddresses;
import static org.simplejavamail.internal.util.MiscUtil.valueNullOrEmpty;
import static org.simplejavamail.internal.util.Preconditions.checkNonEmptyArgument;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_BCC_ADDRESS;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_BCC_NAME;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_CC_ADDRESS;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_CC_NAME;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_FROM_ADDRESS;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_FROM_NAME;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_REPLYTO_ADDRESS;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_REPLYTO_NAME;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_SUBJECT;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_TO_ADDRESS;
import static org.simplejavamail.util.ConfigLoader.Property.DEFAULT_TO_NAME;
import static org.simplejavamail.util.ConfigLoader.getProperty;
import static org.simplejavamail.util.ConfigLoader.hasProperty;
/**
* Email message with all necessary data for an effective mailing action, including attachments etc.
*
* @author Benny Bottema
*/
@SuppressWarnings("SameParameterValue")
public class Email {
/**
* The sender of the email. Can be used in conjunction with {@link #replyToRecipient}.
*/
private Recipient fromRecipient;
/**
* The reply-to-address, optional. Can be used in conjunction with {@link #fromRecipient}.
*/
private Recipient replyToRecipient;
/**
* The email message body in plain text.
*/
private String text;
/**
* The email message body in html.
*/
private String textHTML;
/**
* The subject of the email message.
*/
private String subject;
/**
* List of {@link Recipient}.
*/
private final List<Recipient> recipients;
/**
* List of {@link AttachmentResource}.
*/
private final List<AttachmentResource> embeddedImages;
/**
* List of {@link AttachmentResource}.
*/
private final List<AttachmentResource> attachments;
/**
* Map of header name and values, such as <code>X-Priority</code> etc.
*/
private final Map<String, String> headers;
/*
DKIM properties
*/
private boolean applyDKIMSignature = false;
private InputStream dkimPrivateKeyInputStream;
private File dkimPrivateKeyFile; // supported seperately, so we don't have to do resource management ourselves for the InputStream
private String signingDomain;
private String selector;
/**
* Constructor, creates all internal lists. Populates default from, reply-to, to, cc and bcc if provided in the config file.
*/
public Email() {
this(true);
}
public Email(final boolean readFromDefaults) {
recipients = new ArrayList<>();
embeddedImages = new ArrayList<>();
attachments = new ArrayList<>();
headers = new HashMap<>();
if (readFromDefaults) {
if (hasProperty(DEFAULT_FROM_ADDRESS)) {
setFromAddress((String) getProperty(DEFAULT_FROM_NAME), (String) getProperty(DEFAULT_FROM_ADDRESS));
}
if (hasProperty(DEFAULT_REPLYTO_ADDRESS)) {
setReplyToAddress((String) getProperty(DEFAULT_REPLYTO_NAME), (String) getProperty(DEFAULT_REPLYTO_ADDRESS));
}
if (hasProperty(DEFAULT_TO_ADDRESS)) {
if (hasProperty(DEFAULT_TO_NAME)) {
addRecipient((String) getProperty(DEFAULT_TO_NAME), (String) getProperty(DEFAULT_TO_ADDRESS), RecipientType.TO);
} else {
addRecipients((String) getProperty(DEFAULT_TO_ADDRESS), RecipientType.TO);
}
}
if (hasProperty(DEFAULT_CC_ADDRESS)) {
if (hasProperty(DEFAULT_CC_NAME)) {
addRecipient((String) getProperty(DEFAULT_CC_NAME), (String) getProperty(DEFAULT_CC_ADDRESS), RecipientType.CC);
} else {
addRecipients((String) getProperty(DEFAULT_CC_ADDRESS), RecipientType.CC);
}
}
if (hasProperty(DEFAULT_BCC_ADDRESS)) {
if (hasProperty(DEFAULT_BCC_NAME)) {
addRecipient((String) getProperty(DEFAULT_BCC_NAME), (String) getProperty(DEFAULT_BCC_ADDRESS), RecipientType.BCC);
} else {
addRecipients((String) getProperty(DEFAULT_BCC_ADDRESS), RecipientType.BCC);
}
}
if (hasProperty(DEFAULT_SUBJECT)) {
setSubject((String) getProperty(DEFAULT_SUBJECT));
}
}
}
/**
* As {@link #signWithDomainKey(InputStream, String, String)}, but with a File reference that is later read as {@code InputStream}.
*/
@SuppressWarnings("WeakerAccess")
public void signWithDomainKey(@Nonnull final File dkimPrivateKeyFile, @Nonnull final String signingDomain, @Nonnull final String selector) {
this.applyDKIMSignature = true;
this.dkimPrivateKeyFile = checkNonEmptyArgument(dkimPrivateKeyFile, "dkimPrivateKeyFile");
this.signingDomain = checkNonEmptyArgument(signingDomain, "signingDomain");
this.selector = checkNonEmptyArgument(selector, "selector");
}
/**
* Primes this email for signing with a DKIM domain key. Actual signing is done when sending using a <code>Mailer</code>.
* <p/>
* Also see:
* <pre><ul>
* <li>https://postmarkapp.com/guides/dkim</li>
* <li>https://github.com/markenwerk/java-utils-mail-dkim</li>
* <li>http://www.gettingemaildelivered.com/dkim-explained-how-to-set-up-and-use-domainkeys-identified-mail-effectively</li>
* <li>https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail</li>
* </ul></pre>
*
* @param dkimPrivateKeyInputStream De key content used to sign for the sending party.
* @param signingDomain The domain being authorized to send.
* @param selector Additional domain specifier.
*/
@SuppressWarnings("WeakerAccess")
public void signWithDomainKey(@Nonnull final InputStream dkimPrivateKeyInputStream, @Nonnull final String signingDomain, @Nonnull final String selector) {
this.applyDKIMSignature = true;
this.dkimPrivateKeyInputStream = checkNonEmptyArgument(dkimPrivateKeyInputStream, "dkimPrivateKeyInputStream");
this.signingDomain = checkNonEmptyArgument(signingDomain, "signingDomain");
this.selector = checkNonEmptyArgument(selector, "selector");
}
/**
* Sets the sender address.
*
* @param name The sender's name.
* @param fromAddress The sender's email address.
*/
public void setFromAddress(@Nullable final String name, @Nonnull final String fromAddress) {
fromRecipient = new Recipient(name, checkNonEmptyArgument(fromAddress, "fromAddress"), null);
}
/**
* Sets the reply-to address (optional).
*
* @param name The replied-to-receiver name.
* @param replyToAddress The replied-to-receiver email address.
*/
public void setReplyToAddress(@Nullable final String name, @Nonnull final String replyToAddress) {
replyToRecipient = new Recipient(name, checkNonEmptyArgument(replyToAddress, "replyToAddress"), null);
}
/**
* Bean setter for {@link #subject}.
*/
public void setSubject(@Nonnull final String subject) {
this.subject = checkNonEmptyArgument(subject, "subject");
}
/**
* Bean setter for {@link #text}.
*/
public void setText(@Nullable final String text) {
this.text = text;
}
/**
* Bean setter for {@link #textHTML}.
*/
public void setTextHTML(@Nullable final String textHTML) {
this.textHTML = textHTML;
}
/**
* Adds a new {@link Recipient} to the list on account of name, address and recipient type (eg. {@link RecipientType#CC}).
*
* @param name The name of the recipient.
* @param address The emailadres of the recipient.
* @param type The type of receiver (eg. {@link RecipientType#CC}).
* @see #recipients
* @see Recipient
* @see RecipientType
*/
public void addRecipient(@Nullable final String name, @Nonnull final String address, @Nonnull final RecipientType type) {
checkNonEmptyArgument(address, "address");
checkNonEmptyArgument(type, "type");
recipients.add(new Recipient(name, address, type));
}
/**
* Adds a new {@link Recipient} to the list on account of name, address and recipient type (eg. {@link RecipientType#CC}).
*
* @param emailAddressList The emailadres of the recipient or recipients separated by comma or semicolon.
* @param type The type of receiver (eg. {@link RecipientType#CC}).
* @see #recipients
* @see Recipient
* @see RecipientType
*/
public void addRecipients(@Nonnull final String emailAddressList, @Nonnull final RecipientType type) {
checkNonEmptyArgument(type, "type");
checkNonEmptyArgument(emailAddressList, "emailAddressList");
addRecipients(type, extractEmailAddresses(emailAddressList));
}
/**
* Adds all given recipients addresses to the list on account of address and recipient type (eg. {@link RecipientType#CC}).
*
* @param recipientEmailAddressesToAdd List of preconfigured recipients.
* @see #recipients
* @see Recipient
* @see RecipientType
*/
public void addRecipients(@Nonnull final RecipientType type, @Nonnull final String... recipientEmailAddressesToAdd) {
checkNonEmptyArgument(type, "type");
for (final String emailAddress : checkNonEmptyArgument(recipientEmailAddressesToAdd, "recipientEmailAddressesToAdd")) {
recipients.add(new Recipient(null, emailAddress, type));
}
}
/**
* Adds all given {@link Recipient} instances to the list (as copies) on account of name, address and recipient type (eg. {@link RecipientType#CC}).
*
* @param recipientsToAdd List of preconfigured recipients.
* @see #recipients
* @see Recipient
* @see RecipientType
*/
public void addRecipients(@Nonnull final Recipient... recipientsToAdd) {
for (final Recipient recipient : checkNonEmptyArgument(recipientsToAdd, "recipientsToAdd")) {
final String address = checkNonEmptyArgument(recipient.getAddress(), "recipient.address");
final RecipientType type = checkNonEmptyArgument(recipient.getType(), "recipient.type");
recipients.add(new Recipient(recipient.getName(), address, type));
}
}
/**
* Adds an embedded image (attachment type) to the email message and generates the necessary {@link DataSource} with the given byte data. Then
* delegates to {@link #addEmbeddedImage(String, DataSource)}. At this point the datasource is actually a {@link ByteArrayDataSource}.
*
* @param name The name of the image as being referred to from the message content body (eg. 'signature').
* @param data The byte data of the image to be embedded.
* @param mimetype The content type of the given data (eg. "image/gif" or "image/jpeg").
* @see ByteArrayDataSource
* @see #addEmbeddedImage(String, DataSource)
*/
public void addEmbeddedImage(@Nonnull final String name, @Nonnull final byte[] data, @Nonnull final String mimetype) {
checkNonEmptyArgument(name, "name");
checkNonEmptyArgument(data, "data");
checkNonEmptyArgument(mimetype, "mimetype");
final ByteArrayDataSource dataSource = new ByteArrayDataSource(data, mimetype);
dataSource.setName(name);
addEmbeddedImage(name, dataSource);
}
/**
* Overloaded method which sets an embedded image on account of name and {@link DataSource}.
*
* @param name The name of the image as being referred to from the message content body (eg. 'embeddedimage').
* @param imagedata The image data.
*/
@SuppressWarnings("WeakerAccess")
public void addEmbeddedImage(@Nullable final String name, @Nonnull final DataSource imagedata) {
checkNonEmptyArgument(imagedata, "imagedata");
if (valueNullOrEmpty(name) && valueNullOrEmpty(imagedata.getName())) {
throw new EmailException(EmailException.NAME_MISSING_FOR_EMBEDDED_IMAGE);
}
embeddedImages.add(new AttachmentResource(name, imagedata));
}
/**
* Adds a header to the {@link #headers} list. The value is stored as a <code>String</code>. example: <code>email.addHeader("X-Priority",
* 2)</code>
*
* @param name The name of the header.
* @param value The value of the header, which will be stored using {@link String#valueOf(Object)}.
*/
@SuppressWarnings("WeakerAccess")
public void addHeader(@Nonnull final String name, @Nonnull final Object value) {
checkNonEmptyArgument(name, "name");
checkNonEmptyArgument(value, "value");
headers.put(name, String.valueOf(value));
}
/**
* Adds an attachment to the email message and generates the necessary {@link DataSource} with the given byte data. Then delegates to {@link
* #addAttachment(String, DataSource)}. At this point the datasource is actually a {@link ByteArrayDataSource}.
*
* @param name The name of the extension (eg. filename including extension).
* @param data The byte data of the attachment.
* @param mimetype The content type of the given data (eg. "plain/text", "image/gif" or "application/pdf").
* @see ByteArrayDataSource
* @see #addAttachment(String, DataSource)
*/
public void addAttachment(@Nonnull final String name, @Nonnull final byte[] data, @Nonnull final String mimetype) {
checkNonEmptyArgument(name, "name");
checkNonEmptyArgument(data, "data");
checkNonEmptyArgument(mimetype, "mimetype");
final ByteArrayDataSource dataSource = new ByteArrayDataSource(data, mimetype);
dataSource.setName(MiscUtil.encodeText(name));
addAttachment(MiscUtil.encodeText(name), dataSource);
}
/**
* Overloaded method which sets an attachment on account of name and {@link DataSource}.
*
* @param name The name of the attachment (eg. 'filename.ext').
* @param filedata The attachment data.
*/
public void addAttachment(@Nullable final String name, @Nonnull final DataSource filedata) {
checkNonEmptyArgument(filedata, "filedata");
attachments.add(new AttachmentResource(MiscUtil.encodeText(name), filedata));
}
/**
* Bean getter for {@link #fromRecipient}.
*/
public Recipient getFromRecipient() {
return fromRecipient;
}
/**
* Bean getter for {@link #replyToRecipient}.
*/
public Recipient getReplyToRecipient() {
return replyToRecipient;
}
/**
* Bean getter for {@link #subject}.
*/
public String getSubject() {
return subject;
}
/**
* Bean getter for {@link #text}.
*/
public String getText() {
return text;
}
/**
* Bean getter for {@link #textHTML}.
*/
public String getTextHTML() {
return textHTML;
}
/**
* Bean getter for {@link #attachments} as unmodifiable list.
*/
public List<AttachmentResource> getAttachments() {
return Collections.unmodifiableList(attachments);
}
/**
* Bean getter for {@link #embeddedImages} as unmodifiable list.
*/
public List<AttachmentResource> getEmbeddedImages() {
return Collections.unmodifiableList(embeddedImages);
}
/**
* Bean getter for {@link #recipients} as unmodifiable list.
*/
public List<Recipient> getRecipients() {
return Collections.unmodifiableList(recipients);
}
/**
* Bean getter for {@link #headers} as unmodifiable map.
*/
public Map<String, String> getHeaders() {
return Collections.unmodifiableMap(headers);
}
public boolean isApplyDKIMSignature() {
return applyDKIMSignature;
}
public InputStream getDkimPrivateKeyInputStream() {
return dkimPrivateKeyInputStream;
}
public File getDkimPrivateKeyFile() {
return dkimPrivateKeyFile;
}
public String getSigningDomain() {
return signingDomain;
}
public String getSelector() {
return selector;
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean equals(final Object o) {
return (this == o) || (o != null && getClass() == o.getClass() &&
EqualsHelper.equalsEmail(this, (Email) o));
}
@Override
public String toString() {
return "Email{" +
"\n\tfromRecipient=" + fromRecipient +
",\n\treplyToRecipient=" + replyToRecipient +
",\n\ttext='" + text + '\'' +
",\n\ttextHTML='" + textHTML + '\'' +
",\n\tsubject='" + subject + '\'' +
",\n\trecipients=" + recipients +
",\n\tembeddedImages=" + embeddedImages +
",\n\tattachments=" + attachments +
",\n\theaders=" + headers +
"\n}";
}
/**
* Constructor for the Builder class
*
* @param builder The builder from which to create the email.
*/
Email(@Nonnull final EmailBuilder builder) {
checkNonEmptyArgument(builder, "builder");
recipients = builder.getRecipients();
embeddedImages = builder.getEmbeddedImages();
attachments = builder.getAttachments();
headers = builder.getHeaders();
fromRecipient = builder.getFromRecipient();
replyToRecipient = builder.getReplyToRecipient();
text = builder.getText();
textHTML = builder.getTextHTML();
subject = builder.getSubject();
if (builder.getDkimPrivateKeyFile() != null) {
signWithDomainKey(builder.getDkimPrivateKeyFile(), builder.getSigningDomain(), builder.getSelector());
} else if (builder.getDkimPrivateKeyInputStream() != null) {
signWithDomainKey(builder.getDkimPrivateKeyInputStream(), builder.getSigningDomain(), builder.getSelector());
}
}
}