/*
* @(#)Email.java
*
* Copyright 2009 Instituto Superior Tecnico
* Founding Authors: Luis Cruz
*
* https://fenix-ashes.ist.utl.pt/
*
* This file is part of the E-mail SMTP Adapter Module.
*
* The E-mail SMTP Adapter Module is free software: you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.
*
* The E-mail Module is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the E-mail Module. If not, see .
*
*/
package pt.ist.emailNotifier.domain;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import org.joda.time.DateTime;
import pt.ist.bennu.core._development.PropertiesManager;
import pt.ist.emailNotifier.util.EmailAddressList;
import pt.utl.ist.fenix.tools.util.StringAppender;
/**
*
* @author Luis Cruz
* @author Paulo Abrantes
*
*/
public class Email extends Email_Base {
private static Session SESSION = null;
private static int MAX_MAIL_RECIPIENTS;
private static synchronized Session init() {
final Properties properties = new Properties();
properties.put("mail.smtp.host", PropertiesManager.getProperty("mail.smtp.host"));
properties.put("mail.smtp.name", PropertiesManager.getProperty("mail.smtp.name"));
properties.put("mailSender.max.recipients", PropertiesManager.getProperty("mailSender.max.recipients"));
properties.put("mailingList.host.name", PropertiesManager.getProperty("mailingList.host.name"));
properties.put("mail.debug", "true");
final Session tempSession = Session.getDefaultInstance(properties, null);
for (final Entry entry : tempSession.getProperties().entrySet()) {
System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());
}
MAX_MAIL_RECIPIENTS = Integer.parseInt(properties.getProperty("mailSender.max.recipients"));
SESSION = tempSession;
return SESSION;
}
public Email() {
super();
setEmailNotifier(EmailNotifier.getInstance());
}
public Email(final String fromName, final String fromAddress, final String subject, final String body, String... toAddresses) {
this(fromName, fromAddress, null, Arrays.asList(toAddresses), null, null, subject, body);
}
public Email(final String fromName, final String fromAddress, final String[] replyTos, final Collection toAddresses,
final Collection ccAddresses, final Collection bccAddresses, final String subject, final String body) {
this(fromName, fromAddress, replyTos, toAddresses, ccAddresses, bccAddresses, subject, body, null);
}
public Email(final String fromName, final String fromAddress, final String[] replyTos, final Collection toAddresses,
final Collection ccAddresses, final Collection bccAddresses, final String subject, final String body,
final String htmlBody) {
this();
setFromName(fromName);
setFromAddress(fromAddress);
setReplyTos(new EmailAddressList(replyTos == null ? null : Arrays.asList(replyTos)));
setToAddresses(new EmailAddressList(toAddresses));
setCcAddresses(new EmailAddressList(ccAddresses));
setBccAddresses(new EmailAddressList(bccAddresses));
setSubject(subject);
setBody(body);
setHtmlBody(htmlBody);
}
public void delete() {
for (final MessageTransportResult messageTransportResult : getMessageTransportResultSet()) {
messageTransportResult.delete();
}
for (final MessageId messageId : getMessageIdsSet()) {
messageId.delete();
}
removeEmailNotifier();
super.deleteDomainObject();
}
public String[] replyTos() {
return getReplyTos() == null ? null : getReplyTos().toArray();
}
public Collection toAddresses() {
return getToAddresses() == null ? null : getToAddresses().toCollection();
}
public Collection ccAddresses() {
return getCcAddresses() == null ? null : getCcAddresses().toCollection();
}
public Collection bccAddresses() {
return getBccAddresses() == null ? null : getBccAddresses().toCollection();
}
private void logProblem(final String description) {
new MessageTransportResult(this, null, description);
}
private void logProblem(final MessagingException e) {
logProblem(e.getMessage());
final Exception nextException = e.getNextException();
if (nextException != null) {
if (nextException instanceof MessagingException) {
logProblem((MessagingException) nextException);
} else {
logProblem(nextException.getMessage());
}
}
}
private void abort() {
final Collection failed = new HashSet();
final EmailAddressList failedAddresses = getFailedAddresses();
if (failedAddresses != null && !failedAddresses.isEmpty()) {
failed.addAll(failedAddresses.toCollection());
}
final EmailAddressList toAddresses = getToAddresses();
if (toAddresses != null && !toAddresses.isEmpty()) {
failed.addAll(toAddresses.toCollection());
}
final EmailAddressList ccAddresses = getCcAddresses();
if (ccAddresses != null && !ccAddresses.isEmpty()) {
failed.addAll(ccAddresses.toCollection());
}
final EmailAddressList bccAddresses = getBccAddresses();
if (bccAddresses != null && !bccAddresses.isEmpty()) {
failed.addAll(bccAddresses.toCollection());
}
final EmailAddressList emailAddressList = new EmailAddressList(failed);
setFailedAddresses(emailAddressList);
setToAddresses(null);
setCcAddresses(null);
setBccAddresses(null);
}
private void retry(final EmailAddressList toAddresses, final EmailAddressList ccAddresses, final EmailAddressList bccAddresses) {
setToAddresses(toAddresses);
setCcAddresses(ccAddresses);
setBccAddresses(bccAddresses);
}
private static String encode(final String string) {
try {
return string == null ? "" : MimeUtility.encodeText(string);
} catch (final UnsupportedEncodingException e) {
e.printStackTrace();
return string;
}
}
protected static String constructFromString(final String fromName, String fromAddress) {
return (fromName == null || fromName.length() == 0) ? fromAddress : StringAppender.append(fromName.replace(',', ' '),
" <", fromAddress, ">");
}
private class EmailMimeMessage extends MimeMessage {
private String fenixMessageId = null;
public EmailMimeMessage() {
super(SESSION == null ? init() : SESSION);
}
@Override
public String getMessageID() throws MessagingException {
if (fenixMessageId == null) {
final String externalId = getExternalId();
fenixMessageId = externalId + "." + new DateTime().getMillis() + "@fenix";
}
return fenixMessageId;
}
@Override
protected void updateMessageID() throws MessagingException {
setHeader("Message-ID", getMessageID());
}
public void send(final Email email) throws MessagingException {
if (email.getFromName() == null) {
logProblem("error.from.address.cannot.be.null");
abort();
return;
}
final String from = constructFromString(encode(email.getFromName()), email.getFromAddress());
final String[] replyTos = email.replyTos();
final Address[] replyToAddresses = new Address[replyTos == null ? 0 : replyTos.length];
if (replyTos != null) {
for (int i = 0; i < replyTos.length; i++) {
try {
replyToAddresses[i] = new InternetAddress(encode(replyTos[i]));
} catch (final AddressException e) {
logProblem("invalid.reply.to.address: " + replyTos[i]);
abort();
return;
}
}
}
setFrom(new InternetAddress(from));
setSubject(encode(email.getSubject()));
setReplyTo(replyToAddresses);
final MimeMultipart mimeMultipart = new MimeMultipart();
final String htmlBody = getHtmlBody();
if (htmlBody != null && !htmlBody.trim().isEmpty()) {
final BodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(htmlBody, "text/html; charset='utf-8'");
mimeMultipart.addBodyPart(bodyPart);
}
final String body = email.getBody();
if (body != null && !body.trim().isEmpty()) {
final BodyPart bodyPart = new MimeBodyPart();
bodyPart.setText(body);
mimeMultipart.addBodyPart(bodyPart);
}
setContent(mimeMultipart);
addRecipientsAux();
new MessageId(getEmail(), getMessageID());
Transport.send(this);
final Address[] allRecipients = getAllRecipients();
setConfirmedAddresses(allRecipients);
}
private void addRecipientsAux() {
boolean hasAnyToOrCC = false;
if (hasAnyRecipients(getToAddresses())) {
final EmailAddressList tos = getToAddresses();
final EmailAddressList remainder = addRecipientsAux(RecipientType.TO, tos);
setToAddresses(remainder);
hasAnyToOrCC = true;
}
if (hasAnyRecipients(getCcAddresses())) {
final EmailAddressList ccs = getCcAddresses();
final EmailAddressList remainder = addRecipientsAux(RecipientType.CC, ccs);
setCcAddresses(remainder);
hasAnyToOrCC = true;
}
if (!hasAnyToOrCC && hasAnyRecipients(getBccAddresses())) {
final EmailAddressList bccs = getBccAddresses();
final EmailAddressList remainder = addRecipientsAux(RecipientType.BCC, bccs);
setBccAddresses(remainder);
}
}
private EmailAddressList addRecipientsAux(final javax.mail.Message.RecipientType recipientType,
final EmailAddressList emailAddressList) {
final String[] emailAddresses = emailAddressList.toArray();
for (int i = 0; i < emailAddresses.length; i++) {
final String emailAddress = emailAddresses[i];
try {
if (emailAddressFormatIsValid(emailAddress)) {
addRecipient(recipientType, new InternetAddress(encode(emailAddress)));
} else {
logProblem("invalid.email.address.format: " + emailAddress);
}
} catch (final AddressException e) {
logProblem(e.getMessage() + " " + emailAddress);
} catch (final MessagingException e) {
logProblem(e.getMessage() + " " + emailAddress);
}
if (i == MAX_MAIL_RECIPIENTS && i + 1 < emailAddresses.length) {
final String all = emailAddressList.toString();
final int next = all.indexOf(emailAddress) + emailAddress.length() + 2;
return new EmailAddressList(all.substring(next));
}
}
return null;
}
public boolean emailAddressFormatIsValid(String emailAddress) {
if ((emailAddress == null) || (emailAddress.length() == 0)) {
return false;
}
if (emailAddress.indexOf(' ') > 0) {
return false;
}
String[] atSplit = emailAddress.split("@");
if (atSplit.length != 2) {
return false;
} else if ((atSplit[0].length() == 0) || (atSplit[1].length() == 0)) {
return false;
}
String domain = new String(atSplit[1]);
if (domain.lastIndexOf('.') == (domain.length() - 1)) {
return false;
}
if (domain.indexOf('.') <= 0) {
return false;
}
return true;
}
}
private Email getEmail() {
return this;
}
private void setConfirmedAddresses(final Address[] recipients) {
final Collection addresses = new HashSet();
final EmailAddressList confirmedAddresses = getConfirmedAddresses();
if (confirmedAddresses != null && !confirmedAddresses.isEmpty()) {
addresses.addAll(confirmedAddresses.toCollection());
}
if (recipients != null) {
for (final Address address : recipients) {
addresses.add(address.toString());
}
}
setConfirmedAddresses(new EmailAddressList(addresses));
}
private void setFailedAddresses(final Address[] recipients) {
final Collection addresses = new HashSet();
final EmailAddressList failedAddresses = getFailedAddresses();
if (failedAddresses != null && !failedAddresses.isEmpty()) {
addresses.addAll(failedAddresses.toCollection());
}
if (recipients != null) {
for (final Address address : recipients) {
addresses.add(address.toString());
}
}
setFailedAddresses(new EmailAddressList(addresses));
}
private void resend(final Address[] recipients) {
// final Collection addresses = new HashSet();
// final EmailAddressList bccAddresses = getBccAddresses();
// if (bccAddresses != null && !bccAddresses.isEmpty()) {
// addresses.addAll(bccAddresses.toCollection());
// }
if (recipients != null) {
for (final Address address : recipients) {
final String[] replyTos = getReplyTos() == null ? null : getReplyTos().toArray();
final Email email =
new Email(getFromName(), getFromAddress(), replyTos, Collections.EMPTY_SET, Collections.EMPTY_SET,
Collections.singleton(address.toString()), getSubject(), getBody());
// TODO : it will be resent but we can no longer connect it to the message...
// email.setMessage(getMessage());
//addresses.add(address.toString());
}
}
// setBccAddresses(new EmailAddressList(addresses));
}
public void deliver() {
// if (hasMessage() && getMessage().getCreated().plusDays(5).isBeforeNow()) {
// removeEmailNotifier();
// } else {
final EmailAddressList toAddresses = getToAddresses();
final EmailAddressList ccAddresses = getCcAddresses();
final EmailAddressList bccAddresses = getBccAddresses();
final EmailMimeMessage emailMimeMessage = new EmailMimeMessage();
try {
emailMimeMessage.send(this);
} catch (final SendFailedException e) {
logProblem(e);
final Address[] invalidAddresses = e.getInvalidAddresses();
setFailedAddresses(invalidAddresses);
final Address[] validSentAddresses = e.getValidSentAddresses();
setConfirmedAddresses(validSentAddresses);
final Address[] validUnsentAddresses = e.getValidUnsentAddresses();
resend(validUnsentAddresses);
} catch (final MessagingException e) {
logProblem(e);
// abort();
retry(toAddresses, ccAddresses, bccAddresses);
}
if (!hasAnyRecipients()) {
removeEmailNotifier();
}
// }
}
private boolean hasAnyRecipients() {
return hasAnyRecipients(getToAddresses()) || hasAnyRecipients(getCcAddresses()) || hasAnyRecipients(getBccAddresses());
}
private boolean hasAnyRecipients(final EmailAddressList emailAddressList) {
return emailAddressList != null && !emailAddressList.isEmpty();
}
}