/*
* This is eMonocot, a global online biodiversity information resource.
*
* Copyright © 2011–2015 The Board of Trustees of the Royal Botanic Gardens, Kew and The University of Oxford
*
* eMonocot is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* eMonocot 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 Affero General Public License for more details.
*
* The complete text of the GNU Affero General Public License is in the source repository as the file
* ‘COPYING’. It is also available from <http://www.gnu.org/licenses/>.
*/
package org.emonocot.harvest.integration;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.InternetAddress;
import org.emonocot.api.CommentService;
import org.emonocot.api.UserService;
import org.emonocot.harvest.common.HtmlSanitizer;
import org.emonocot.model.Base;
import org.emonocot.model.BaseData;
import org.emonocot.model.Comment;
import org.emonocot.model.auth.User;
import org.emonocot.model.registry.Organisation;
import org.emonocot.model.registry.Resource;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.message.GenericMessage;
/**
* @author jk00kg
*
*/
public class EmailServiceHelper {
Logger logger = LoggerFactory.getLogger(EmailServiceHelper.class);
Pattern pattern = Pattern
.compile("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}");
static Set<String> autoReplyPrecedenceValues = new HashSet<String>();
static Set<String> autoReplySubjectValues = new HashSet<String>();
static {
autoReplyPrecedenceValues.add("auto_reply");
autoReplyPrecedenceValues.add("bulk");
autoReplyPrecedenceValues.add("junk");
autoReplySubjectValues.add("Auto:");
autoReplySubjectValues.add("Automatic reply");
autoReplySubjectValues.add("Autosvar");
autoReplySubjectValues.add("Automatisk svar");
autoReplySubjectValues.add("Automatisch antwoord");
autoReplySubjectValues.add("Abwesenheitsnotiz");
autoReplySubjectValues.add("Risposta Non al computer");
autoReplySubjectValues.add("Auto Response");
autoReplySubjectValues.add("Respuesta automática");
autoReplySubjectValues.add("Fuori sede");
autoReplySubjectValues.add("Out of Office");
autoReplySubjectValues.add("Frånvaro");
autoReplySubjectValues.add("Réponse automatique");
}
private final String HEADER_TEMPLATE_NAME = "templateName";
private String defaultTemplateName;
private CommentService commentService;
private UserService userService;
/**
* A mapping between logical template/view names used in this class and
* resolvable template locations
*/
private Map<String, String> templates;
private HtmlSanitizer htmlSanitizer;
/**
* @param defaultTemplateName
* the defaultTemplateName to set
*/
public void setDefaultTemplateName(String defaultTemplateName) {
this.defaultTemplateName = defaultTemplateName;
}
/**
* @param templates
* the templates to set
*/
public void setTemplates(Map<String, String> templates) {
this.templates = templates;
}
@Autowired
public void setCommentService(CommentService commentService) {
this.commentService = commentService;
}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired
public void setHtmlSanitizer(HtmlSanitizer htmlSanitizer) {
this.htmlSanitizer = htmlSanitizer;
}
/**
* Create a comment replying to an incoming email
*
* @param message
* @return
* @throws Exception
*/
public Message<Comment> createReply(Message<javax.mail.Message> message) throws Exception {
javax.mail.Message email = message.getPayload();
String subject = email.getSubject();
Matcher matcher = pattern.matcher(subject);
if(matcher.find()) {
Comment comment = new Comment();
comment.setIdentifier(UUID.randomUUID().toString());
comment.setCreated(new DateTime());
String identifier = matcher.group();
Comment inResponseTo = commentService.find(identifier,"aboutData");
comment.setInResponseTo(inResponseTo);
if(inResponseTo != null) {
comment.setCommentPage(inResponseTo.getCommentPage());
comment.setAboutData(inResponseTo.getAboutData());
if(comment.getAboutData() != null && comment.getAboutData() instanceof BaseData) {
} else {
logger.debug("Response is about " + comment.getAboutData() + " not creating reply");
return null;
}
} else {
logger.debug("Could not find comment with identifier " + identifier + " not creating reply");
return null;
}
comment.setComment(htmlSanitizer.sanitize(getText(email)));
for(Address address : email.getFrom()) {
if(address instanceof InternetAddress) {
InternetAddress internetAddress = (InternetAddress)address;
User user = userService.find(internetAddress.getAddress());
if(user != null) {
comment.setUser(user);
break;
}
}
}
comment.setStatus(Comment.Status.PENDING);
String content = comment.getComment();
if(content != null && content.length() > 256) {
content = content.substring(0,255);
}
logger.debug("Recieved comment from " + comment.getUser() + " in response to " + comment.getInResponseTo() + " content " + content);
Map<String, Object> headers = new HashMap<String, Object>();
headers.putAll(message.getHeaders());
return new GenericMessage<Comment>(comment, headers);
} else {
String content = getText(email);
if(content != null && content.length() > 256) {
content = content.substring(0,255);
}
logger.debug("Recieved comment from " + email.getFrom()[0].toString() + " with subject " + email.getSubject() + " and content " + content);
return null;
}
}
/**
* http://stackoverflow.com/questions/1027395/detecting-outlook-autoreply-out-of-office-emails
* @param message
* @return
*/
@Filter
public boolean filterOutOfOfficeReplies(@Payload javax.mail.Message message) {
try {
if(message.getHeader("x-auto-response-suppress") != null) {
logger.debug("Email contains header x-auto-response-suppress, filtering");
return false;
}
if(message.getHeader("x-autorespond") != null) {
logger.debug("Email contains header x-autorespond, filtering");
return false;
}
if(message.getHeader("precedence") != null) {
for(String header : message.getHeader("precedence")) {
if(autoReplyPrecedenceValues.contains(header)) {
logger.debug("Email contains header precedence = " + header +", filtering");
return false;
}
}
}
if(message.getHeader("x-precedence") != null) {
for(String header : message.getHeader("x-precedence")) {
if(autoReplyPrecedenceValues.contains(header)) {
logger.debug("Email contains header x-precedence = " + header +", filtering");
return false;
}
}
}
if(message.getHeader("auto-submitted") != null) {
for(String header : message.getHeader("auto-submitted")) {
if(header.equals("auto-replied")) {
logger.debug("Email contains auto-submitted = " + header +", filtering");
return false;
}
}
}
if(message.getSubject() != null) {
for(String subjectValue: this.autoReplySubjectValues) {
if(message.getSubject().startsWith(subjectValue)) {
logger.debug("Email contains subject " + message.getSubject() +", filtering");
return false;
}
}
}
return true;
} catch(MessagingException me) {
logger.error("MessagingException thrown trying to filter message" + me.getLocalizedMessage());
return false;
}
}
private String getText(Part part) throws MessagingException, IOException {
if (part.isMimeType("text/*")) {
return (String) part.getContent();
}
if (part.isMimeType("multipart/alternative")) {
// prefer html text over plain text
Multipart multipart = (Multipart) part.getContent();
String text = null;
for (int i = 0; i < multipart.getCount(); i++) {
Part bodyPart = multipart.getBodyPart(i);
if (bodyPart.isMimeType("text/plain")) {
if (text == null)
text = getText(bodyPart);
continue;
} else if (bodyPart.isMimeType("text/html")) {
String string = getText(bodyPart);
if (string != null)
return string;
} else {
return getText(bodyPart);
}
}
return text;
} else if (part.isMimeType("multipart/*")) {
Multipart multipart = (Multipart) part.getContent();
for (int i = 0; i < multipart.getCount(); i++) {
String string = getText(multipart.getBodyPart(i));
if (string != null)
return string;
}
}
return null;
}
/**
* Prepare a message for outgoing email
*
* @param message
* @return
*/
public Message<Map> prepareMessage(Message<?> message) {
Map<String, Object> model = new HashMap<String, Object>();
Map<String, Object> headers = new HashMap<String, Object>();
headers.putAll(message.getHeaders());
Object payload = message.getPayload();
if (payload instanceof Comment) {
model.put("comment", payload);
// Decide which template
Base about = ((Comment) payload).getAboutData();
String templateName = null;
if(((Comment) payload).getInResponseTo() != null) {
templateName = "reply";
} else if (about instanceof BaseData) {
templateName = "comment";
} else if (about instanceof Resource) {
templateName = "resource";
}
if (templateName != null) {
headers.put(HEADER_TEMPLATE_NAME, templates.get(templateName));
} else {
headers.put(HEADER_TEMPLATE_NAME,
templates.get(defaultTemplateName));
}
}
return new GenericMessage<Map>(model, headers);
}
@Filter
public boolean preventSelfSending(@Header("toAddress") String toAddress, @Payload Comment comment) {
if(comment.getAuthority() != null) {
Organisation authority = comment.getAuthority();
if(authority.getIdentifier().equals(toAddress)) {
return false;
}
if(authority.getCommentsEmailedTo() != null && authority.getCommentsEmailedTo().equals(toAddress)) {
return false;
}
}
return true;
}
@Router
public String getDestinationChannel(@Header("toAddress") String toAddress) {
if(toAddress.startsWith("http://")) {
return "scratchpad";
} else {
return "email";
}
}
}