package org.gbif.occurrence.download.service;
import org.gbif.api.model.common.User;
import org.gbif.api.model.occurrence.Download;
import org.gbif.api.service.common.UserService;
import org.gbif.occurrence.download.service.freemarker.NiceDateTemplateMethodModel;
import org.gbif.occurrence.query.HumanFilterBuilder;
import org.gbif.occurrence.query.TitleLookup;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.gbif.occurrence.download.service.Constants.NOTIFY_ADMIN;
/**
* Utility class that sends notification emails of occurrence downloads.
*/
public class DownloadEmailUtils {
private static final Logger LOG = LoggerFactory.getLogger(DownloadEmailUtils.class);
private static final Splitter EMAIL_SPLITTER = Splitter.on(';').omitEmptyStrings().trimResults();
private static final String SUCCESS_SUBJECT = "Your GBIF data download is ready";
private static final String ERROR_SUBJECT = "Your GBIF data download failed";
private final Configuration freemarker = new Configuration();
private final UserService userService;
private final Set<Address> bccAddresses;
private final URI portalUrl;
private final Session session;
private final TitleLookup titleLookup;
@Inject
public DownloadEmailUtils(@Named("mail.bcc") String bccAddresses, @Named("portal.url") String portalUrl,
UserService userService, Session session, TitleLookup titleLookup) {
this.userService = userService;
this.titleLookup = titleLookup;
this.bccAddresses = Sets.newHashSet(toInternetAddresses(EMAIL_SPLITTER.split(bccAddresses)));
this.session = session;
this.portalUrl = URI.create(portalUrl);
setupFreemarker();
}
private void setupFreemarker() {
freemarker.setDefaultEncoding("UTF-8");
freemarker.setLocale(Locale.US);
freemarker.setNumberFormat("0.####");
freemarker.setDateFormat("yyyy-mm-dd");
// create custom rendering for relative dates
freemarker.setSharedVariable("niceDate", new NiceDateTemplateMethodModel());
freemarker.setClassForTemplateLoading(DownloadEmailUtils.class, "/email");
}
/**
* Sends an email notifying that an error occurred while creating the download file.
*/
public void sendErrorNotificationMail(Download download) {
sendNotificationMail(download, ERROR_SUBJECT, "error.ftl");
}
/**
* Sends an email notifying that the occurrence download is ready.
*/
public void sendSuccessNotificationMail(Download download) {
sendNotificationMail(download, SUCCESS_SUBJECT, "success.ftl");
}
@VisibleForTesting
protected String buildBody(Download download, String bodyTemplate) throws IOException, TemplateException {
// Prepare the E-Mail body text
StringWriter contentBuffer = new StringWriter();
Template template = freemarker.getTemplate(bodyTemplate);
template.process(new EmailModel(download, portalUrl, getHumanQuery(download)), contentBuffer);
return contentBuffer.toString();
}
/**
* Gets a human readable version of the occurrence search query used.
*/
public String getHumanQuery(Download download) {
return new HumanFilterBuilder(titleLookup).humanFilterString(download.getRequest().getPredicate());
}
/**
* Gets the list of notification addresses from the download object.
* If the list of addresses is empty, the email of the creator is used.
*/
private List<Address> getNotificationAddresses(Download download) {
List<Address> emails = Lists.newArrayList();
if (download.getRequest().getNotificationAddresses() == null
|| download.getRequest().getNotificationAddresses().isEmpty()) {
User user = userService.get(download.getRequest().getCreator());
if (user != null) {
try {
emails.add(new InternetAddress(user.getEmail()));
} catch (AddressException e) {
// bad address?
LOG.warn("Ignore corrupt email address {}", user.getEmail());
}
}
} else {
emails = toInternetAddresses(download.getRequest().getNotificationAddresses());
}
return emails;
}
/**
* Utility method that sends a notification email.
*/
private void sendNotificationMail(Download download, String subject, String bodyTemplate) {
List<Address> emails = getNotificationAddresses(download);
if (emails.isEmpty() && bccAddresses.isEmpty()) {
LOG.warn("No valid notification addresses given for download {}", download.getKey());
return;
}
try {
// Send E-Mail
MimeMessage msg = new MimeMessage(session);
msg.setFrom();
msg.setRecipients(Message.RecipientType.TO, emails.toArray(new Address[emails.size()]));
msg.setRecipients(Message.RecipientType.BCC, bccAddresses.toArray(new Address[bccAddresses.size()]));
msg.setSubject(subject);
msg.setSentDate(new Date());
msg.setText(buildBody(download, bodyTemplate));
Transport.send(msg);
} catch (TemplateException | IOException e) {
LOG.error(NOTIFY_ADMIN, "Rendering of notification Mail for download [{}] failed", download.getKey(), e);
} catch (MessagingException e) {
LOG.error(NOTIFY_ADMIN, "Sending of notification Mail for download [{}] failed", download.getKey(), e);
}
}
/**
* Transforms a iterable of string into a list of email addresses.
*/
private static List<Address> toInternetAddresses(Iterable<String> strEmails) {
List<Address> emails = Lists.newArrayList();
for (String address : strEmails) {
try {
emails.add(new InternetAddress(address));
} catch (AddressException e) {
// bad address?
LOG.warn("Ignore corrupt email address {}", address);
}
}
return emails;
}
}