/*
* FrontlineSMS <http://www.frontlinesms.com>
* Copyright 2007, 2008 kiwanja
*
* This file is part of FrontlineSMS.
*
* FrontlineSMS 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.
*
* FrontlineSMS 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 FrontlineSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package net.frontlinesms;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.apache.log4j.Logger;
import net.frontlinesms.data.domain.*;
import net.frontlinesms.email.EmailUtils;
import net.frontlinesms.listener.EmailListener;
/**
* This class is used to send e-mails.
*
* TODO refactor to email package; UNIT TEST
*
* @author Carlos Eduardo Genz
* <li> kadu(at)masabi(dot)com
*/
public class EmailSender extends Thread {
private static final boolean DEBUG_SESSION = false;
private boolean running;
private final ConcurrentLinkedQueue<Email> outbox = new ConcurrentLinkedQueue<Email>();
private EmailListener emailListener;
private int retries = 6;
private long SLEEP_TIME = 30000;
private String server;
private String account;
private int serverPort;
private String password;
private boolean isSSL;
private EmailAccount sender;
private static Logger LOG = FrontlineUtils.getLogger(EmailSender.class);
public EmailSender(EmailAccount account, EmailListener listener) {
super("EmailSender :: " + account.getAccountName());
this.sender = account;
this.emailListener = listener;
}
/**
* Adds the supplied email to the outbox.
*
* @param email
*/
public void sendEmail(Email email) {
LOG.trace("ENTER");
email.setStatus(Email.Status.PENDING);
outbox.add(email);
LOG.debug("E-mail added to outbox. Size is [" + outbox.size() + "]");
if (emailListener != null) {
emailListener.outgoingEmailEvent(this, email);
}
LOG.trace("EXIT");
}
public void run() {
LOG.trace("ENTER");
running = true;
while (running) {
if (outbox.size() > 0) {
this.server = sender.getAccountServer();
this.account = sender.getAccountName();
this.password = sender.getAccountPassword();
this.isSSL = sender.useSsl();
if (sender.getAccountServerPort() == -1) {
// This is to fix the old email settings on db, when we created the new column, the default value is -1.
if (isSSL) sender.setAccountServerPort(EmailAccount.DEFAULT_SMTPS_PORT);
else sender.setAccountServerPort(EmailAccount.DEFAULT_SMTP_PORT);
}
this.serverPort = sender.getAccountServerPort();
send();
}
FrontlineUtils.sleep_ignoreInterrupts(5000);
}
LOG.trace("EXIT");
}
/**
* Stops this thread
*/
public void stopRunning() {
running = false;
}
/**
* Sends an Email using the supplied information.
*/
private void send() {
LOG.trace("ENTER");
java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
Session session = getSession();
Transport transport = null;
boolean everythingOk = true;
boolean connectionOk = true;
LOG.debug("Number of retries [" + retries + "]");
if (retries > 0) {
//If this server has more retries, try to connect.
try {
transport = connect(session);
} catch (MessagingException e) {
LOG.info("Fail to connect to server [" + server + "]");
LOG.debug("Fail to connect to server [" + server + "]", e);
connectionOk = false;
everythingOk = false;
retries--;
}
}
List<Email> toAdd = new ArrayList<Email>();
Email email;
while ((email = outbox.poll()) != null) {
//Construct the message
long sent = FrontlineSMSConstants.DEFAULT_END_DATE;
try {
if (connectionOk) {
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(account));
String recipients[] = email.getEmailRecipients().split(";");
for (String recipient : recipients) {
msg.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient));
}
msg.setSubject(email.getEmailSubject());
msg.setText(email.getEmailContent());
Date date = new Date();
msg.setSentDate(date);
sent = date.getTime();
LOG.debug("Sending e-mail [" + email + "]");
transport.sendMessage(msg, msg.getAllRecipients());
}
} catch (AddressException e) {
LOG.info("Fail to send e-mail [" + email.getEmailContent() + "] to [" + email.getEmailRecipients() + "]");
LOG.debug("Fail to send e-mail [" + email.getEmailContent() + "] to [" + email.getEmailRecipients() + "]", e);
everythingOk = false;
} catch (IllegalArgumentException e) {
LOG.info("Fail to send e-mail [" + email.getEmailContent() + "] to [" + email.getEmailRecipients() + "]");
LOG.debug("Fail to send e-mail [" + email.getEmailContent() + "] to [" + email.getEmailRecipients() + "]", e);
everythingOk = false;
} catch (MessagingException e) {
LOG.info("Fail to send e-mail [" + email.getEmailContent() + "] to [" + email.getEmailRecipients() + "]");
LOG.debug("Fail to send e-mail [" + email.getEmailContent() + "] to [" + email.getEmailRecipients() + "]", e);
everythingOk = false;
}
if (everythingOk) {
email.setDate(sent);
email.setStatus(Email.Status.SENT);
LOG.debug("E-mail [" + email + "] was sent!");
} else if (connectionOk || retries == 0) {
//Failed to send this e-mail, due to either sending failure or no more retries.
LOG.debug("E-mail [" + email + "] was not sent! Setting status to FAILED.");
email.setStatus(Email.Status.FAILED);
} else {
//We still have some retries for this server, so we should re-try to send e-mails.
LOG.debug("E-mail [" + email + "] was not sent! Setting status to RE-TRYING.");
email.setStatus(Email.Status.RETRYING);
toAdd.add(email);
}
if (emailListener != null) {
emailListener.outgoingEmailEvent(this, email);
}
}
if (!connectionOk && retries == 0) {
//Connection errors
LOG.debug("This server has run out fo connection re-tries. Removing thread!");
stopRunning();
} else if (!connectionOk && retries > 0) {
LOG.debug("This server could not be reached. Waiting [" + SLEEP_TIME + "ms] to try again!");
FrontlineUtils.sleep_ignoreInterrupts(SLEEP_TIME);
for (Email mail : toAdd) {
outbox.add(mail);
}
LOG.debug("Thread is back to try connection again!");
}
LOG.trace("EXIT");
}
/**
* Connects to the server
*
* @param session
* @return
* @throws MessagingException
* @throws MessagingException
*/
private Transport connect(Session session) throws MessagingException {
LOG.trace("ENTER");
LOG.debug("Host [" + server + "]");
LOG.debug("Port [" + serverPort + "]");
LOG.debug("Account [" + account + "]");
Transport transport = session.getTransport();
transport.connect(server, serverPort, account, password);
LOG.debug("Connection OK!");
LOG.trace("EXIT");
return transport;
}
/**
* Returns a new session for this server.
*
* @return
*/
private Session getSession() {
LOG.trace("ENTER");
LOG.debug("Host [" + server + "]");
LOG.debug("Use SSL [" + isSSL + "]");
Properties props = EmailUtils.getPropertiesForHost(server, serverPort, isSSL);
Session session;
if (isSSL) {
session = Session.getInstance(props);
} else {
session = Session.getInstance(props, new EmailSender.FrontlineEmailAuthenticator(account, password));
}
session.setDebug(DEBUG_SESSION);
LOG.trace("EXIT");
return session;
}
/**
* SMTPAuthenticator is used to do simple authentication when the SMTP
* server requires it.
*/
public static class FrontlineEmailAuthenticator extends Authenticator {
private String user;
private String pass;
public FrontlineEmailAuthenticator(String user, String pass) {
this.user = user;
this.pass = pass;
}
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, pass);
}
}
}