/*
* ====================================================================
* Copyright (c) 2005-2012 sventon project. All rights reserved.
*
* This software is licensed as described in the file LICENSE, which
* you should have received as part of this distribution. The terms
* are also available at http://www.sventon.org.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.sventon.mail;
import com.sun.mail.smtp.SMTPTransport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sventon.appl.Application;
import org.sventon.appl.RepositoryConfiguration;
import org.sventon.model.LogEntry;
import org.sventon.model.RepositoryName;
import org.sventon.repository.RepositoryChangeListener;
import org.sventon.repository.RevisionUpdate;
import org.sventon.util.HTMLCreator;
import javax.activation.DataHandler;
import javax.annotation.PostConstruct;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.util.ByteArrayDataSource;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Class for notifying users about new revisions.
*/
public final class MailNotifier implements RepositoryChangeListener {
/**
* The static logging instance.
*/
private static final Log LOGGER = LogFactory.getLog(MailNotifier.class);
/**
* The application.
*/
private Application application;
/**
* Set of receivers.
*/
private final Set<InternetAddress> receivers = new HashSet<InternetAddress>();
/**
* Text replacement token for revision number.
*/
private static final String REVISION_TOKEN = "@@revision@@";
/**
* Text replacement token for the repository name
*/
private static final String NAME_TOKEN = "@@repositoryName@@";
/**
* Date formatter instance.
*/
private DateFormat dateFormat;
/**
* Threshold value that decides if an update is too big to send notification mails.
*/
private int revisionCountThreshold;
private String host;
private int port;
private String from;
private String subject;
private boolean auth;
private boolean ssl;
private String user;
private String password;
/**
* The mail session.
*/
private Session session;
/**
* Base URL where sventon is located.
*/
private String baseURL;
/**
* Executor service.
*/
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
/**
* Initializes the component.
*/
@PostConstruct
public void init() {
final Properties mailProperties = new Properties();
mailProperties.setProperty("mail.smtp.host", host);
mailProperties.setProperty("mail.smtp.port", String.valueOf(port));
mailProperties.setProperty("mail.smtp.auth", auth ? "true" : "false");
session = Session.getInstance(mailProperties, null);
session.setDebug(LOGGER.isDebugEnabled());
}
@Override
public void update(final RevisionUpdate revisionUpdate) {
executorService.execute(new Runnable() {
public void run() {
final List<LogEntry> revisions = revisionUpdate.getRevisions();
if (revisions.size() > revisionCountThreshold) {
LOGGER.info("Update contains more than max allowed updates, ["
+ revisionCountThreshold + "]. No notification mail sent");
return;
}
for (final LogEntry logEntry : revisions) {
if (logEntry.isAccessible()) {
final RepositoryName repositoryName = revisionUpdate.getRepositoryName();
final RepositoryConfiguration configuration = application.getConfiguration(repositoryName);
LOGGER.info("Sending notification mail for [" + repositoryName + "], revision: " + logEntry.getRevision());
sendMailMessage(logEntry, repositoryName, configuration);
}
}
}
});
}
private void sendMailMessage(LogEntry logEntry, RepositoryName repositoryName, RepositoryConfiguration configuration) {
try {
final Message msg = createMessage(logEntry, repositoryName, configuration.getMailTemplate());
final SMTPTransport transport = (SMTPTransport) session.getTransport(ssl ? "smtps" : "smtp");
try {
if (auth) {
transport.connect(host, user, password);
} else {
transport.connect();
}
transport.sendMessage(msg, msg.getAllRecipients());
LOGGER.debug("Notification mail was sent successfully");
} finally {
transport.close();
}
} catch (Exception e) {
LOGGER.error("Unable to send notification mail", e);
}
}
/**
* @param logEntry Log entry
* @param repositoryName Name
* @param mailTemplate Template
* @return Message
* @throws MessagingException If a message exception occurs.
* @throws IOException if a IO exception occurs while creating the data source.
*/
private Message createMessage(final LogEntry logEntry, RepositoryName repositoryName, String mailTemplate)
throws MessagingException, IOException {
final Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
msg.setRecipients(Message.RecipientType.BCC, receivers.toArray(new InternetAddress[receivers.size()]));
msg.setSubject(formatSubject(subject, logEntry.getRevision(), repositoryName));
msg.setDataHandler(new DataHandler(new ByteArrayDataSource(HTMLCreator.createRevisionDetailBody(
mailTemplate, logEntry, baseURL, repositoryName, dateFormat, null), "text/html")));
msg.setHeader("X-Mailer", "sventon");
msg.setSentDate(new Date());
return msg;
}
/**
* Creates the mail subject.
*
* @param subject Subject string.
* @param revision Revision.
* @param repositoryName Repository name.
* @return Substituted subject string.
*/
protected String formatSubject(final String subject, final long revision, final RepositoryName repositoryName) {
String result = subject;
result = result.replace(REVISION_TOKEN, String.valueOf(revision));
result = result.replace(NAME_TOKEN, repositoryName.toString());
return result;
}
/**
* Sets the base URL used when creating HTML anchor links back to
* the installed sventon application.
*
* @param baseURL Base URL, eg. http://yourserver.com/svn/.
*/
public void setBaseURL(final String baseURL) {
this.baseURL = baseURL;
}
/**
* Sets the date format.
*
* @param dateFormat Date format.
*/
public void setDateFormat(final String dateFormat) {
this.dateFormat = new SimpleDateFormat(dateFormat);
}
/**
* Sets the SMTP mail host.
*
* @param host Mail host.
*/
public void setHost(final String host) {
this.host = host;
}
/**
* Sets the SMTP mail host port.
*
* @param port Mail host port.
*/
public void setPort(final int port) {
this.port = port;
}
/**
* Sets the sender address of the mail notification.
*
* @param from Sender's address.
*/
public void setFrom(final String from) {
this.from = from;
}
/**
* Sets the notification mail subject.
*
* @param subject Subject.
*/
public void setSubject(final String subject) {
this.subject = subject;
}
/**
* Sets the mail notification receivers addresses.
*
* @param receivers List of the receiver's email addresses.
*/
public void setReceivers(final List<String> receivers) {
for (final String receiver : receivers) {
try {
this.receivers.add(new InternetAddress(receiver));
} catch (AddressException e) {
LOGGER.warn("Unable to parse email address: " + receiver, e);
}
}
}
/**
* Set to true if authorization is required.
*
* @param auth True/false
*/
public void setAuth(final boolean auth) {
this.auth = auth;
}
/**
* Set to true if SSL should be used.
*
* @param ssl True/false
*/
public void setSsl(final boolean ssl) {
this.ssl = ssl;
}
/**
* Sets the SMTP host user.
*
* @param user User.
*/
public void setUser(final String user) {
this.user = user;
}
/**
* Sets the SMTP host user's password.
*
* @param password Password.
*/
public void setPassword(final String password) {
this.password = password;
}
/**
* Sets the threshold value that decides if an update is too big to
* send notification mails.
*
* @param revisionCountThreshold Threshold value. If an update contains more
* revisions than the given threshold, no notification
* mail will be sent.
*/
public void setRevisionCountThreshold(final int revisionCountThreshold) {
this.revisionCountThreshold = revisionCountThreshold;
}
/**
* Sets the application.
*
* @param application Application
*/
public void setApplication(final Application application) {
this.application = application;
}
}