/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.util.mail.manager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.zip.Adler32;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Multipart;
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.persistence.TemporalType;
import javax.persistence.TypedQuery;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.olat.basesecurity.IdentityImpl;
import org.olat.basesecurity.IdentityRef;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.persistence.DB;
import org.olat.core.commons.services.notifications.NotificationsManager;
import org.olat.core.commons.services.notifications.Publisher;
import org.olat.core.commons.services.notifications.PublisherData;
import org.olat.core.commons.services.notifications.Subscriber;
import org.olat.core.commons.services.notifications.SubscriptionContext;
import org.olat.core.commons.services.taskexecutor.model.DBSecureRunnable;
import org.olat.core.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.UserConstants;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.Encoder;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.filter.impl.NekoHTMLFilter;
import org.olat.core.util.mail.ContactList;
import org.olat.core.util.mail.MailAttachment;
import org.olat.core.util.mail.MailBundle;
import org.olat.core.util.mail.MailContent;
import org.olat.core.util.mail.MailContext;
import org.olat.core.util.mail.MailHelper;
import org.olat.core.util.mail.MailManager;
import org.olat.core.util.mail.MailModule;
import org.olat.core.util.mail.MailTemplate;
import org.olat.core.util.mail.MailerResult;
import org.olat.core.util.mail.MailerSMTPAuthenticator;
import org.olat.core.util.mail.model.DBMail;
import org.olat.core.util.mail.model.DBMailAttachment;
import org.olat.core.util.mail.model.DBMailImpl;
import org.olat.core.util.mail.model.DBMailLight;
import org.olat.core.util.mail.model.DBMailLightImpl;
import org.olat.core.util.mail.model.DBMailRecipient;
import org.olat.core.util.mail.model.SimpleMailContent;
import org.olat.core.util.vfs.FileStorage;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.sun.mail.smtp.SMTPMessage;
/**
*
* Description:<br>
* Manager which send e-mails, make the triage between mails which are
* really send by POP, or only saved in the intern mail system (a.k.a on
* the database).
*
* <P>
* Initial Date: 24 mars 2011 <br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*/
@Service("mailManager")
public class MailManagerImpl implements MailManager, InitializingBean {
private static final OLog log = Tracing.createLoggerFor(MailManagerImpl.class);
public static final String MAIL_TEMPLATE_FOLDER = "/customizing/mail/";
private VelocityEngine velocityEngine;
@Autowired
private DB dbInstance;
@Autowired @Qualifier("mailAsyncExecutorService")
private ExecutorService asyncExecutor;
@Autowired
private NotificationsManager notificationsManager;
private final MailModule mailModule;
private FileStorage attachmentStorage;
@Autowired
public MailManagerImpl(MailModule mailModule) {
this.mailModule = mailModule;
}
@Override
public void afterPropertiesSet() throws Exception {
VFSContainer root = mailModule.getRootForAttachments();
attachmentStorage = new FileStorage(root);
PublisherData pdata = getPublisherData();
SubscriptionContext scontext = getSubscriptionContext();
notificationsManager.getOrCreatePublisher(scontext, pdata);
Properties p = null;
try {
velocityEngine = new VelocityEngine();
p = new Properties();
p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.SimpleLog4JLogSystem");
p.setProperty(RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS, "org.olat.core.gui.render.velocity.InfinispanResourceCache");
p.setProperty("runtime.log.logsystem.log4j.category", "syslog");
velocityEngine.init(p);
} catch (Exception e) {
throw new RuntimeException("config error " + p.toString());
}
}
@Override
public SubscriptionContext getSubscriptionContext() {
return new SubscriptionContext("Inbox", 0l, "");
}
@Override
public PublisherData getPublisherData() {
String data = "";
String businessPath = "[Inbox:0]";
PublisherData publisherData = new PublisherData("Inbox", data, businessPath);
return publisherData;
}
@Override
public Subscriber getSubscriber(Identity identity) {
SubscriptionContext context = getSubscriptionContext();
if(context == null) return null;
Publisher publisher = notificationsManager.getPublisher(context);
if(publisher == null) {
return null;
}
return notificationsManager.getSubscriber(identity, publisher);
}
@Override
public void subscribe(Identity identity) {
PublisherData data = getPublisherData();
SubscriptionContext context = getSubscriptionContext();
if(context != null) {
notificationsManager.subscribe(identity, context, data);
}
}
@Override
public DBMail getMessageByKey(Long key) {
StringBuilder sb = new StringBuilder();
sb.append("select mail from ").append(DBMailImpl.class.getName()).append(" mail")
.append(" left join fetch mail.recipients recipients")
.append(" where mail.key=:mailKey");
List<DBMail> mails = dbInstance.getCurrentEntityManager()
.createQuery(sb.toString(), DBMail.class)
.setParameter("mailKey", key)
.getResultList();
if(mails.isEmpty()) return null;
return mails.get(0);
}
@Override
public List<DBMailAttachment> getAttachments(DBMailLight mail) {
StringBuilder sb = new StringBuilder();
sb.append("select attachment from ").append(DBMailAttachment.class.getName()).append(" attachment")
.append(" inner join attachment.mail mail")
.append(" where mail.key=:mailKey");
return dbInstance.getCurrentEntityManager()
.createQuery(sb.toString(), DBMailAttachment.class)
.setParameter("mailKey", mail.getKey())
.getResultList();
}
private DBMailAttachment getAttachment(Long key) {
StringBuilder sb = new StringBuilder();
sb.append("select attachment from ").append(DBMailAttachment.class.getName()).append(" attachment")
.append(" where attachment.key=:attachmentKey");
List<DBMailAttachment> attachments = dbInstance.getCurrentEntityManager()
.createQuery(sb.toString(), DBMailAttachment.class)
.setParameter("attachmentKey", key)
.getResultList();
if(attachments.isEmpty()) {
return null;
}
return attachments.get(0);
}
@Override
public String saveAttachmentToStorage(String name, String mimetype, long checksum, long size, InputStream stream) {
String hasSibling = getAttachmentSibling(name, mimetype, checksum, size);
if(StringHelper.containsNonWhitespace(hasSibling)) {
return hasSibling;
}
String uuid = Encoder.md5hash(name + checksum);
String dir = attachmentStorage.generateDir(uuid, false);
VFSContainer container = attachmentStorage.getContainer(dir);
String uniqueName = VFSManager.similarButNonExistingName(container, name);
VFSLeaf file = container.createChildLeaf(uniqueName);
VFSManager.copyContent(stream, file);
return dir + uniqueName;
}
private String getAttachmentSibling(String name, String mimetype, long checksum, long size) {
StringBuilder sb = new StringBuilder();
sb.append("select attachment from ").append(DBMailAttachment.class.getName()).append(" attachment")
.append(" where attachment.checksum=:checksum and attachment.size=:size and attachment.name=:name");
if(mimetype == null) {
sb.append(" and attachment.mimetype is null");
} else {
sb.append(" and attachment.mimetype=:mimetype");
}
TypedQuery<DBMailAttachment> query = dbInstance.getCurrentEntityManager()
.createQuery(sb.toString(), DBMailAttachment.class)
.setParameter("checksum", new Long(checksum))
.setParameter("size", new Long(size))
.setParameter("name", name);
if(mimetype != null) {
query.setParameter("mimetype", mimetype);
}
List<DBMailAttachment> attachments = query.getResultList();
if(attachments.isEmpty()) {
return null;
}
return attachments.get(0).getPath();
}
private int countAttachment(String path) {
StringBuilder sb = new StringBuilder();
sb.append("select count(attachment) from ").append(DBMailAttachment.class.getName()).append(" attachment")
.append(" where attachment.path=:path");
return dbInstance.getCurrentEntityManager()
.createQuery(sb.toString(), Number.class)
.setParameter("path", path)
.getSingleResult().intValue();
}
@Override
public VFSLeaf getAttachmentDatas(Long key) {
DBMailAttachment attachment = getAttachment(key);
return getAttachmentDatas(attachment);
}
@Override
public VFSLeaf getAttachmentDatas(MailAttachment attachment) {
String path = attachment.getPath();
if(StringHelper.containsNonWhitespace(path)) {
VFSContainer root = mailModule.getRootForAttachments();
VFSItem item = root.resolve(path);
if(item instanceof VFSLeaf) {
return (VFSLeaf)item;
}
}
return null;
}
@Override
public boolean hasNewMail(Identity identity) {
StringBuilder sb = new StringBuilder();
sb.append("select count(mail) from ").append(DBMailImpl.class.getName()).append(" mail")
.append(" inner join mail.recipients recipient")
.append(" inner join recipient.recipient recipientIdentity")
.append(" where recipientIdentity.key=:recipientKey and recipient.read=false and recipient.deleted=false");
Number count = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Number.class)
.setParameter("recipientKey", identity.getKey())
.getSingleResult();
return count.intValue() > 0;
}
/**
*
* @param mail
* @param read cannot be null
* @param identity
* @return true if the read flag has been changed
*/
@Override
public boolean setRead(DBMailLight mail, Boolean read, Identity identity) {
if(mail == null || read == null || identity == null) throw new NullPointerException();
boolean changed = false;
for(DBMailRecipient recipient:mail.getRecipients()) {
if(recipient == null) continue;
if(recipient.getRecipient() != null && recipient.getRecipient().equalsByPersistableKey(identity)) {
if(!read.equals(recipient.getRead())) {
recipient.setRead(read);
dbInstance.updateObject(recipient);
changed |= true;
}
}
}
return changed;
}
@Override
public DBMailLight toggleRead(DBMailLight mail, Identity identity) {
Boolean read = null;
for(DBMailRecipient recipient:mail.getRecipients()) {
if(recipient == null) continue;
if(recipient.getRecipient() != null && recipient.getRecipient().equalsByPersistableKey(identity)) {
if(read == null) {
read = recipient.getRead() == null ? Boolean.FALSE : recipient.getRead();
}
recipient.setRead(read.booleanValue() ? Boolean.FALSE : Boolean.TRUE);
dbInstance.updateObject(recipient);
}
}
return mail;
}
/**
* @param mail
* @param marked cannot be null
* @param identity
* @return true if the marked flag has been changed
*/
@Override
public boolean setMarked(DBMailLight mail, Boolean marked, Identity identity) {
if(mail == null || marked == null || identity == null) throw new NullPointerException();
boolean changed = false;
for(DBMailRecipient recipient:mail.getRecipients()) {
if(recipient == null) continue;
if(recipient != null && recipient.getRecipient() != null && recipient.getRecipient().equalsByPersistableKey(identity)) {
if(marked == null) {
marked = Boolean.FALSE;
}
if(!marked.equals(recipient.getMarked())) {
recipient.setMarked(marked.booleanValue());
dbInstance.updateObject(recipient);
changed |= true;
}
}
}
return changed;
}
@Override
public DBMailLight toggleMarked(DBMailLight mail, Identity identity) {
Boolean marked = null;
for(DBMailRecipient recipient:mail.getRecipients()) {
if(recipient == null) continue;
if(recipient != null && recipient.getRecipient() != null && recipient.getRecipient().equalsByPersistableKey(identity)) {
if(marked == null) {
marked = recipient.getMarked() == null ? Boolean.FALSE : recipient.getMarked();
}
recipient.setMarked(marked.booleanValue() ? Boolean.FALSE : Boolean.TRUE);
dbInstance.updateObject(recipient);
}
}
return mail;
}
/**
* Set the mail as deleted for a user
* @param mail
* @param identity
*/
@Override
public void delete(DBMailLight mail, Identity identity, boolean deleteMetaMail) {
if(mail == null) return;//already deleted
if(StringHelper.containsNonWhitespace(mail.getMetaId()) && deleteMetaMail) {
List<DBMailLight> mails = getEmailsByMetaId(mail.getMetaId());
for(DBMailLight childMail:mails) {
deleteMail(childMail, identity, false);
}
} else {
deleteMail(mail, identity, false);
}
}
private void deleteMail(DBMailLight mail, Identity identity, boolean forceRemoveRecipient) {
boolean delete = true;
List<DBMailRecipient> updates = new ArrayList<DBMailRecipient>();
if(mail.getFrom() != null && mail.getFrom().getRecipient() != null) {
if(identity.equalsByPersistableKey(mail.getFrom().getRecipient())) {
DBMailRecipient from = mail.getFrom();
from.setDeleted(Boolean.TRUE);
if(forceRemoveRecipient) {
from.setRecipient(null);
}
updates.add(from);
}
if(mail.getFrom().getDeleted() != null) {
delete &= mail.getFrom().getDeleted().booleanValue();
}
}
for(DBMailRecipient recipient:mail.getRecipients()) {
if(recipient == null) continue;
if(recipient.getRecipient() != null && recipient.getRecipient().equalsByPersistableKey(identity)) {
recipient.setDeleted(Boolean.TRUE);
if(forceRemoveRecipient) {
recipient.setRecipient(null);
}
updates.add(recipient);
}
if(recipient.getDeleted() != null) {
delete &= recipient.getDeleted().booleanValue();
}
}
if(delete) {
Set<String> paths = new HashSet<String>();
//all marked as deleted -> delete the mail
List<DBMailAttachment> attachments = getAttachments(mail);
for(DBMailAttachment attachment: attachments) {
mail = attachment.getMail();//reload from the hibernate session
dbInstance.deleteObject(attachment);
if(StringHelper.containsNonWhitespace(attachment.getPath())) {
paths.add(attachment.getPath());
}
}
dbInstance.deleteObject(mail);
//try to remove orphans file
for(String path:paths) {
int count = countAttachment(path);
if(count == 0) {
VFSItem item = mailModule.getRootForAttachments().resolve(path);
if(item instanceof VFSLeaf) {
((VFSLeaf)item).delete();
}
}
}
} else {
for(DBMailRecipient update:updates) {
dbInstance.updateObject(update);
}
}
}
/**
* Load all mails with the identity as from, mail which are not deleted
* for this user. Recipients are loaded.
* @param from
* @param firstResult
* @param maxResults
* @return
*/
@Override
public List<DBMailLight> getOutbox(Identity from, int firstResult, int maxResults, boolean fetchRecipients) {
StringBuilder sb = new StringBuilder();
sb.append("select distinct(mail) from ").append(DBMailLightImpl.class.getName()).append(" mail")
.append(" inner join fetch mail.from fromRecipient")
.append(" inner join fromRecipient.recipient fromRecipientIdentity")
.append(" inner join ").append(fetchRecipients ? "fetch" : "").append(" mail.recipients recipient")
.append(" inner join ").append(fetchRecipients ? "fetch" : "").append(" recipient.recipient recipientIdentity")
.append(" where fromRecipientIdentity.key=:fromKey and fromRecipient.deleted=false and recipientIdentity.key!=:fromKey")
.append(" order by mail.creationDate desc");
TypedQuery<DBMailLight> query = dbInstance.getCurrentEntityManager()
.createQuery(sb.toString(), DBMailLight.class)
.setParameter("fromKey", from.getKey());
if(maxResults > 0) {
query.setMaxResults(maxResults);
}
if(firstResult > 0) {
query.setFirstResult(firstResult);
}
List<DBMailLight> mails = query.getResultList();
return mails;
}
@Override
public List<DBMailLight> getEmailsByMetaId(String metaId) {
if(!StringHelper.containsNonWhitespace(metaId)) return Collections.emptyList();
StringBuilder sb = new StringBuilder();
sb.append("select mail from ").append(DBMailLightImpl.class.getName()).append(" mail")
.append(" inner join fetch mail.from fromRecipient")
.append(" inner join fromRecipient.recipient fromRecipientIdentity")
.append(" where mail.metaId=:metaId");
return dbInstance.getCurrentEntityManager().createQuery(sb.toString(), DBMailLight.class)
.setParameter("metaId", metaId)
.getResultList();
}
/**
* Load all mails with the identity as recipient, only mails which are not deleted
* for this user. Recipients are NOT loaded if not explicitly wanted!
* @param identity
* @param unreadOnly
* @param fetchRecipients
* @param from
* @param firstResult
* @param maxResults
* @return
*/
@Override
public List<DBMailLight> getInbox(IdentityRef identity, Boolean unreadOnly, Boolean fetchRecipients, Date from, int firstResult, int maxResults) {
StringBuilder sb = new StringBuilder();
String fetchOption = (fetchRecipients != null && fetchRecipients.booleanValue()) ? "fetch" : "";
sb.append("select mail from ").append(DBMailLightImpl.class.getName()).append(" mail")
.append(" inner join fetch ").append(" mail.from fromRecipient")
.append(" inner join ").append(fetchOption).append(" mail.recipients recipient")
.append(" left join ").append(fetchOption).append(" recipient.recipient recipientIdentity")
.append(" left join ").append(fetchOption).append(" recipientIdentity.user recipientUser")
.append(" where recipientIdentity.key=:recipientKey and recipient.deleted=false");
if(unreadOnly != null && unreadOnly.booleanValue()) {
sb.append(" and recipient.read=false");
}
if(from != null) {
sb.append(" and mail.creationDate>=:from");
}
sb.append(" order by mail.creationDate desc");
TypedQuery<DBMailLight> query = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), DBMailLight.class)
.setParameter("recipientKey", identity.getKey());
if(maxResults > 0) {
query.setMaxResults(maxResults);
}
if(firstResult > 0) {
query.setFirstResult(firstResult);
}
if(from != null) {
query.setParameter("from", from, TemporalType.TIMESTAMP);
}
List<DBMailLight> mails = query.getResultList();
return mails;
}
@Override
public String getMailTemplate() {
File baseFolder = new File(WebappHelper.getUserDataRoot(), MAIL_TEMPLATE_FOLDER);
File template = new File(baseFolder, "mail_template.html");
if(template.exists()) {
try(InputStream in = new FileInputStream(template)) {
return IOUtils.toString(in);
} catch (IOException e) {
log.error("", e);
}
}
return getDefaultMailTemplate();
}
@Override
public void setMailTemplate(String template) {
File baseFolder = new File(WebappHelper.getUserDataRoot(), MAIL_TEMPLATE_FOLDER);
if(!baseFolder.exists()) {
baseFolder.mkdirs();
}
OutputStream out = null;
try {
File templateFile = new File(baseFolder, "mail_template.html");
StringReader reader = new StringReader(template);
out = new FileOutputStream(templateFile);
IOUtils.copy(reader, out);
} catch (IOException e) {
log.error("", e);
} finally {
IOUtils.closeQuietly(out);
}
}
@Override
public String getDefaultMailTemplate() {
try(InputStream in = MailModule.class.getResourceAsStream("_content/mail_template.html")) {
return IOUtils.toString(in);
} catch (IOException e) {
log.error("Cannot read the default mail template", e);
return null;
}
}
@Override
public MailBundle[] makeMailBundles(MailContext ctxt, List<Identity> recipientsTO,
MailTemplate template, Identity sender, String metaId, MailerResult result) {
List<MailBundle> bundles = new ArrayList<MailBundle>();
if(recipientsTO != null) {
for(Identity recipient: recipientsTO) {
MailBundle bundle = makeMailBundle(ctxt, recipient, template, sender, metaId, result);
if(bundle != null) {
bundles.add(bundle);
}
}
}
return bundles.toArray(new MailBundle[bundles.size()]);
}
@Override
public MailBundle makeMailBundle(MailContext ctxt, Identity recipientTO,
MailTemplate template, Identity sender, String metaId, MailerResult result) {
MailBundle bundle;
if(MailHelper.isDisabledMailAddress(recipientTO, result)) {
bundle = null;//email disabled, nothing to do
} else {
MailContent msg = createWithContext(recipientTO, template, result);
if(msg != null && result.getReturnCode() == MailerResult.OK){
// send mail
bundle = new MailBundle();
bundle.setContext(ctxt);
bundle.setFromId(sender);
bundle.setToId(recipientTO);
bundle.setMetaId(metaId);
bundle.setContent(msg);
} else {
bundle = null;
}
}
return bundle;
}
@Override
public void sendMessageAsync(MailBundle... bundles) {
try {
SendMail sendMail = new SendMail(bundles);
DBSecureRunnable command = new DBSecureRunnable(sendMail);
asyncExecutor.execute(command);
} catch (RejectedExecutionException e) {
log.error("Queue full, email lost", e);
} catch (Exception e) {
log.error("", e);
}
}
@Override
public MailerResult sendMessage(MailBundle... bundles) {
MailerResult result = new MailerResult();
for(MailBundle bundle:bundles) {
MailContent content = decorateMail(bundle);
if(mailModule.isInternSystem()) {
saveDBMessage(bundle.getContext(), bundle.getFromId(), bundle.getFrom(),
bundle.getToId(), bundle.getTo(), bundle.getCc(),
bundle.getContactLists(), bundle.getMetaId(), content, result);
} else {
sendExternMessage(bundle.getFromId(), bundle.getFrom(),
bundle.getToId(), bundle.getTo(), bundle.getCc(),
bundle.getContactLists(), content, result);
}
}
return result;
}
protected MailContent decorateMail(MailBundle bundle) {
MailContent content = bundle.getContent();
String template = getMailTemplate();
boolean htmlTemplate = StringHelper.isHtml(template);
String body = content.getBody();
boolean htmlContent = StringHelper.isHtml(body);
if(htmlTemplate && !htmlContent) {
body = body.replace("&", "&");
body = body.replace("<", "<");
body = body.replace("\n", "<br />");
}
VelocityContext context = new VelocityContext();
context.put("content", body);
context.put("footer", MailHelper.getMailFooter(bundle));
context.put("server", Settings.getServerContextPathURI());
StringWriter writer = new StringWriter(2000);
MailerResult result = new MailerResult();
evaluate(context, template, writer, result);
String decoratedBody;
if(result.isSuccessful()) {
decoratedBody = writer.toString();
} else {
decoratedBody = content.getBody();
}
return new SimpleMailContent(content.getSubject(), decoratedBody, content.getAttachments());
}
protected MailContent createWithContext(Identity recipient, MailTemplate template, MailerResult result) {
VelocityContext context;
if(template != null && template.getContext() != null) {
context = new VelocityContext(template.getContext());
} else {
context = new VelocityContext();
}
template.putVariablesInMailContext(context, recipient);
// merge subject template with context variables
StringWriter subjectWriter = new StringWriter();
evaluate(context, template.getSubjectTemplate(), subjectWriter, result);
// merge body template with context variables
StringWriter bodyWriter = new StringWriter();
evaluate(context, template.getBodyTemplate(), bodyWriter, result);
// check for errors - exit
if (result.getReturnCode() != MailerResult.OK) {
return null;
}
String subject = subjectWriter.toString();
String body = bodyWriter.toString();
List<File> checkedFiles = MailHelper.checkAttachments(template.getAttachments(), result);
File[] attachments = checkedFiles.toArray(new File[checkedFiles.size()]);
return new SimpleMailContent(subject, body, attachments);
}
/**
* Internal Helper: merges a velocity context with a template.
*
* @param context
* @param template
* @param writer writer that contains merged result
* @param mailerResult
*/
protected void evaluate(Context context, String template, StringWriter writer, MailerResult mailerResult) {
try {
boolean result = velocityEngine.evaluate(context, writer, "mailTemplate", template);
if (result) {
mailerResult.setReturnCode(MailerResult.OK);
} else {
log.warn("can't send email from user template with no reason", null);
mailerResult.setReturnCode(MailerResult.TEMPLATE_GENERAL_ERROR);
}
} catch (ParseErrorException e) {
log.warn("can't send email from user template", e);
mailerResult.setReturnCode(MailerResult.TEMPLATE_PARSE_ERROR);
} catch (MethodInvocationException e) {
log.warn("can't send email from user template", e);
mailerResult.setReturnCode(MailerResult.TEMPLATE_GENERAL_ERROR);
} catch (ResourceNotFoundException e) {
log.warn("can't send email from user template", e);
mailerResult.setReturnCode(MailerResult.TEMPLATE_GENERAL_ERROR);
} catch (Exception e) {
log.warn("can't send email from user template", e);
mailerResult.setReturnCode(MailerResult.TEMPLATE_GENERAL_ERROR);
}
}
@Override
public MailerResult forwardToRealInbox(Identity identity, DBMail mail, MailerResult result) {
if(result == null) {
result = new MailerResult();
}
List<DBMailAttachment> attachments = getAttachments(mail);
Address to = createAddress(identity, result, true);
MimeMessage message = createForwardMimeMessage(to, to, mail.getSubject(), mail.getBody(), attachments, result);
if(message != null) {
sendMessage(message, result);
}
return result;
}
@Override
public MailerResult sendExternMessage(MailBundle bundle, MailerResult result, boolean useTemplate) {
MailContent content = bundle.getContent();
if(useTemplate) {
content = decorateMail(bundle);
}
return sendExternMessage(bundle.getFromId(), bundle.getFrom(), bundle.getToId(), bundle.getTo(), bundle.getCc(),
bundle.getContactLists(), content, result);
}
/**
* Send the message via e-mail, always.
* @param from
* @param to
* @param cc
* @param contactLists
* @param listAsBcc
* @param subject
* @param body
* @param attachments
* @return
*/
private MailerResult sendExternMessage(Identity fromId, String from, Identity toId, String to, Identity cc, List<ContactList> bccLists,
MailContent content, MailerResult result) {
if(result == null) {
result = new MailerResult();
}
MimeMessage mail = createMimeMessage(fromId, from, toId, to, cc, bccLists, content, result);
if(mail != null) {
sendMessage(mail, result);
if(result != null && !result.isSuccessful()) {
handleErrors(result, fromId, toId, cc, bccLists);
}
}
return result;
}
private boolean wantRealMailToo(Identity id) {
if(id == null) return false;
String want = id.getUser().getPreferences().getReceiveRealMail();
if(want != null) {
return "true".equals(want);
}
return mailModule.isReceiveRealMailUserDefaultSetting();
}
protected DBMail saveDBMessage(MailContext context, Identity fromId, String from, Identity toId, String to,
Identity cc, List<ContactList> bccLists, String metaId, MailContent content, MailerResult result) {
try {
DBMailImpl mail = new DBMailImpl();
if(result == null) {
result = new MailerResult();
}
boolean makeRealMail = makeRealMail(toId, cc, bccLists);
Address fromAddress = null;
List<Address> toAddress = new ArrayList<Address>();
List<Address> ccAddress = new ArrayList<Address>();
List<Address> bccAddress = new ArrayList<Address>();
if(fromId != null) {
DBMailRecipient fromRecipient = new DBMailRecipient();
fromRecipient.setRecipient(fromId);
if(StringHelper.containsNonWhitespace(from)) {
fromRecipient.setEmailAddress(from);
fromAddress = createFromAddress(from, result);
} else {
fromAddress = createFromAddress(fromId, result);
}
fromRecipient.setVisible(Boolean.TRUE);
fromRecipient.setMarked(Boolean.FALSE);
fromRecipient.setDeleted(Boolean.FALSE);
mail.setFrom(fromRecipient);
} else {
if(!StringHelper.containsNonWhitespace(from)) {
from = WebappHelper.getMailConfig("mailFrom");
}
DBMailRecipient fromRecipient = new DBMailRecipient();
fromRecipient.setEmailAddress(from);
fromRecipient.setVisible(Boolean.TRUE);
fromRecipient.setMarked(Boolean.FALSE);
fromRecipient.setDeleted(Boolean.TRUE);//marked as delted as nobody can read it
mail.setFrom(fromRecipient);
fromAddress = createFromAddress(from, result);
}
if(result.getReturnCode() != MailerResult.OK) {
return null;
}
mail.setMetaId(metaId);
String subject = content.getSubject();
if(subject != null && subject.length() > 500) {
log.warn("Cut a too long subkect in name. Size: " + subject.length(), null);
subject = subject.substring(0, 500);
}
mail.setSubject(subject);
String body = content.getBody();
if(body != null && body.length() > 16777210) {
log.warn("Cut a too long body in mail. Size: " + body.length(), null);
body = body.substring(0, 16000000);
}
mail.setBody(body);
mail.setLastModified(new Date());
if(context != null) {
OLATResourceable ores = context.getOLATResourceable();
if(ores != null) {
String resName = ores.getResourceableTypeName();
if(resName != null && resName.length() > 50) {
log.warn("Cut a too long resourceable type name in mail context: " + resName, null);
resName = resName.substring(0, 49);
}
mail.getContext().setResName(ores.getResourceableTypeName());
mail.getContext().setResId(ores.getResourceableId());
}
String resSubPath = context.getResSubPath();
if(resSubPath != null && resSubPath.length() > 2000) {
log.warn("Cut a too long resSubPath in mail context: " + resSubPath, null);
resSubPath = resSubPath.substring(0, 2000);
}
mail.getContext().setResSubPath(resSubPath);
String businessPath = context.getBusinessPath();
if(businessPath != null && businessPath.length() > 2000) {
log.warn("Cut a too long resSubPath in mail context: " + businessPath, null);
businessPath = businessPath.substring(0, 2000);
}
mail.getContext().setBusinessPath(businessPath);
}
//add to
DBMailRecipient recipientTo = null;
if(toId != null) {
recipientTo = new DBMailRecipient();
if(toId instanceof IdentityImpl) {
recipientTo.setRecipient(toId);
} else {
to = toId.getUser().getProperty(UserConstants.EMAIL, null);
}
if(StringHelper.containsNonWhitespace(to)) {
recipientTo.setEmailAddress(to);
}
recipientTo.setVisible(Boolean.TRUE);
recipientTo.setDeleted(Boolean.FALSE);
recipientTo.setMarked(Boolean.FALSE);
recipientTo.setRead(Boolean.FALSE);
} else if (StringHelper.containsNonWhitespace(to)) {
recipientTo = new DBMailRecipient();
recipientTo.setEmailAddress(to);
recipientTo.setVisible(Boolean.TRUE);
recipientTo.setDeleted(Boolean.TRUE);
recipientTo.setMarked(Boolean.FALSE);
recipientTo.setRead(Boolean.FALSE);
}
if(recipientTo != null) {
mail.getRecipients().add(recipientTo);
createAddress(toAddress, recipientTo, true, result, true);
}
if(makeRealMail && StringHelper.containsNonWhitespace(to)) {
createAddress(toAddress, to);
}
if(cc != null) {
DBMailRecipient recipient = new DBMailRecipient();
if(cc instanceof IdentityImpl) {
recipient.setRecipient(cc);
} else {
recipient.setEmailAddress(cc.getUser().getProperty(UserConstants.EMAIL, null));
}
recipient.setVisible(Boolean.TRUE);
recipient.setDeleted(Boolean.FALSE);
recipient.setMarked(Boolean.FALSE);
recipient.setRead(Boolean.FALSE);
mail.getRecipients().add(recipient);
createAddress(ccAddress, recipient, false, result, true);
}
//add bcc recipients
appendRecipients(mail, bccLists, toAddress, bccAddress, false, makeRealMail, result);
dbInstance.getCurrentEntityManager().persist(mail);
//save attachments
List<File> attachments = content.getAttachments();
if(attachments != null && !attachments.isEmpty()) {
for(File attachment:attachments) {
FileInputStream in = null;
try {
DBMailAttachment data = new DBMailAttachment();
data.setSize(attachment.length());
data.setName(attachment.getName());
long checksum = FileUtils.checksum(attachment, new Adler32()).getValue();
data.setChecksum(new Long(checksum));
data.setMimetype(WebappHelper.getMimeType(attachment.getName()));
in = new FileInputStream(attachment);
String path = saveAttachmentToStorage(data.getName(), data.getMimetype(), checksum, attachment.length(), in);
data.setPath(path);
data.setMail(mail);
dbInstance.getCurrentEntityManager().persist(data);
} catch (FileNotFoundException e) {
log.error("File attachment not found: " + attachment, e);
} catch (IOException e) {
log.error("Error with file attachment: " + attachment, e);
} finally {
IOUtils.closeQuietly(in);
}
}
}
if(makeRealMail) {
//check that we send an email to someone
if(!toAddress.isEmpty() || !ccAddress.isEmpty() || !bccAddress.isEmpty()) {
sendRealMessage(fromAddress, toAddress, ccAddress, bccAddress, subject, body, attachments, result);
if(result != null && !result.isSuccessful()) {
handleErrors(result, fromId, toId, cc, bccLists);
}
}
}
//update subscription
for(DBMailRecipient recipient:mail.getRecipients()) {
if(recipient.getRecipient() != null) {
subscribe(recipient.getRecipient());
}
}
SubscriptionContext subContext = getSubscriptionContext();
notificationsManager.markPublisherNews(subContext, null, false);
return mail;
} catch (AddressException e) {
log.error("Cannot send e-mail: ", e);
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
return null;
}
}
/**
* Basically try to find compare the list of invalid addresses return by the mail server
* with the different recipients (in the form of Identity) of the mail.
*
* @param result The result which contains the invalid addresses
* @param fromId A recipient of the mail (optional can be null)
* @param toId A recipient of the mail (optional can be null)
* @param cc A recipient of the mail (optional can be null)
* @param bccLists A list of contact list of the mail (optional can be null or empty)
*/
private void handleErrors(MailerResult result, Identity fromId, Identity toId, Identity cc, List<ContactList> bccLists) {
if(result == null) return;
List<String> invalidAddresses = result.getInvalidAddresses();
if(invalidAddresses.size() > 0) {
if(match(fromId, invalidAddresses, true)) {
result.addFailedIdentites(fromId);
}
if(match(toId, invalidAddresses, true)) {
result.addFailedIdentites(fromId);
}
if(match(cc, invalidAddresses, true)) {
result.addFailedIdentites(fromId);
}
if(bccLists != null && bccLists.size() > 0) {
for(ContactList bccList:bccLists) {
Map<String,Identity> emailToIdentityMap = bccList.getIdentiEmails();
for(Map.Entry<String,Identity> entry:emailToIdentityMap.entrySet()) {
if(match(entry.getKey(), invalidAddresses, true)) {
result.addFailedIdentites(entry.getValue());
}
}
}
}
}
}
/**
* Try to find the email or institutional email address of the identity in
* the list of addresses.
*
* @param identity The identity
* @param invalidAddresses The list of addresses to compare with
* @param removeMatch if true, the matched address will be removed of the list
* @return true if found
*/
private boolean match(Identity identity, List<String> invalidAddresses, boolean removeMatch) {
boolean match = false;
if(identity != null) {
match |= match(identity.getUser().getEmail(), invalidAddresses, removeMatch);
match |= match(identity.getUser().getProperty(UserConstants.INSTITUTIONALEMAIL, null), invalidAddresses, removeMatch);
}
return match;
}
/**
* Try to find the email address the list of addresses.
*
* @param identity The email to compare
* @param invalidAddresses The list of addresses to compare with
* @param removeMatch if true, the matched address will be removed of the list
* @return true if found
*/
private boolean match(String email, List<String> invalidAddresses, boolean removeMatch) {
if(StringHelper.containsNonWhitespace(email) && invalidAddresses != null) {
for(String invalidAddress:invalidAddresses) {
if(email.toLowerCase().contains(invalidAddress.toLowerCase())) {
if(removeMatch) {
invalidAddresses.remove(invalidAddress);
}
return true;
}
}
}
return false;
}
private void appendRecipients(DBMailImpl mail, List<ContactList> ccLists, List<Address> toAddress, List<Address> ccAddress,
boolean visible, boolean makeRealMail, MailerResult result) throws AddressException {
//append cc/bcc recipients
if(ccLists != null && !ccLists.isEmpty()) {
for(ContactList contactList:ccLists) {
if(makeRealMail && StringHelper.containsNonWhitespace(contactList.getName())) {
Address[] groupAddress = InternetAddress.parse(contactList.getRFC2822Name() + ";");
if(groupAddress != null && groupAddress.length > 0) {
for(Address groupAdd:groupAddress) {
toAddress.add(groupAdd);
}
}
}
for(String email:contactList.getStringEmails().values()) {
DBMailRecipient recipient = new DBMailRecipient();
recipient.setEmailAddress(email);
recipient.setGroup(contactList.getName());
recipient.setVisible(visible);
recipient.setDeleted(Boolean.FALSE);
recipient.setMarked(Boolean.FALSE);
recipient.setRead(Boolean.FALSE);
mail.getRecipients().add(recipient);
if(makeRealMail) {
createAddress(ccAddress, recipient, false, result, false);
}
}
for(Identity identityEmail:contactList.getIdentiEmails().values()) {
DBMailRecipient recipient = new DBMailRecipient();
if(identityEmail instanceof IdentityImpl) {
recipient.setRecipient(identityEmail);
} else {
recipient.setEmailAddress(identityEmail.getUser().getProperty(UserConstants.EMAIL, null));
}
recipient.setGroup(contactList.getName());
recipient.setVisible(visible);
recipient.setDeleted(Boolean.FALSE);
recipient.setMarked(Boolean.FALSE);
recipient.setRead(Boolean.FALSE);
mail.getRecipients().add(recipient);
if(makeRealMail) {
createAddress(ccAddress, recipient, false, result, false);
}
}
}
}
}
private boolean makeRealMail(Identity toId, Identity cc, List<ContactList> bccLists) {
//need real mail to???
boolean makeRealMail = false;
// can occur on self-registration
if (toId == null && cc == null && bccLists == null) return true;
if(toId != null) {
makeRealMail |= wantRealMailToo(toId);
}
if(cc != null) {
makeRealMail |= wantRealMailToo(cc);
}
//add bcc recipients
if(bccLists != null && !bccLists.isEmpty()) {
for(ContactList contactList:bccLists) {
for(Identity identityEmail:contactList.getIdentiEmails().values()) {
makeRealMail |= wantRealMailToo(identityEmail);
}
if(!contactList.getStringEmails().isEmpty()) {
makeRealMail |= true;
}
}
}
return makeRealMail;
}
private MimeMessage createMimeMessage(Identity fromId, String mailFrom, Identity toId, String to, Identity ccId,
List<ContactList> bccLists, MailContent content, MailerResult result) {
try {
Address from;
if(StringHelper.containsNonWhitespace(mailFrom)) {
from = createFromAddress(mailFrom, result);
} else if (fromId != null) {
from = createFromAddress(fromId, result);
} else {
// fxdiff: change from/replyto, see FXOLAT-74 . if no from is set, use default sysadmin-address (adminemail).
from = createAddress(WebappHelper.getMailConfig("mailReplyTo"));
if(from == null) {
log.error("MailConfigError: mailReplyTo is not set", null);
}
}
List<Address> toList = new ArrayList<Address>();
if(StringHelper.containsNonWhitespace(to)) {
Address[] toAddresses = InternetAddress.parse(to);
for(Address toAddress:toAddresses) {
toList.add(toAddress);
}
} else if (toId != null) {
Address toAddress = createAddress(toId, result, true);
if(toAddress != null) {
toList.add(toAddress);
}
}
List<Address> ccList = new ArrayList<Address>();
if(ccId != null) {
Address ccAddress = createAddress(ccId, result, true);
if(ccAddress != null) {
ccList.add(ccAddress);
}
}
//add bcc contact lists
List<Address> bccList = new ArrayList<Address>();
if(bccLists != null) {
for (ContactList contactList : bccLists) {
if(StringHelper.containsNonWhitespace(contactList.getName())) {
Address[] groupNames = InternetAddress.parse(contactList.getRFC2822Name() + ";");
for(Address groupName:groupNames) {
toList.add(groupName);
}
}
Address[] members = contactList.getEmailsAsAddresses();
for(Address member:members) {
bccList.add(member);
}
}
}
Address[] tos = toList.toArray(new Address[toList.size()]);
Address[] ccs = ccList.toArray(new Address[ccList.size()]);
Address[] bccs = bccList.toArray(new Address[bccList.size()]);
return createMimeMessage(from, tos, ccs, bccs, content.getSubject(), content.getBody(), content.getAttachments(), result);
} catch (MessagingException e) {
log.error("", e);
return null;
}
}
private Address createAddress(String address) throws AddressException {
if(address == null) return null;
return new InternetAddress(address);
}
private Address createAddressWithName(String address, String name) throws UnsupportedEncodingException, AddressException {
InternetAddress add = new InternetAddress(address, name);
try {
add.validate();
} catch (AddressException e) {
throw e;
}
return add;
}
private Address createFromAddress(String address, MailerResult result) throws AddressException {
try {
Address add = new InternetAddress(address);
return add;
} catch (AddressException e) {
result.setReturnCode(MailerResult.SENDER_ADDRESS_ERROR);
throw e;
}
}
private boolean createAddress(List<Address> addressList, String address) throws AddressException {
Address add = createAddress(address);
if(add != null) {
addressList.add(add);
}
return true;
}
private boolean createAddress(List<Address> addressList, DBMailRecipient recipient, boolean force, MailerResult result, boolean error) {
String emailAddress = recipient.getEmailAddress();
if(recipient.getRecipient() == null) {
try {
Address address = createAddress(emailAddress);
if(address != null) {
addressList.add(address);
return true;
} else {
if(error) {
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
}
}
} catch (AddressException e) {
if(error) {
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
}
}
} else if(recipient.getRecipient().getStatus() == Identity.STATUS_LOGIN_DENIED) {
result.addFailedIdentites(recipient.getRecipient());
} else {
if(force || wantRealMailToo(recipient.getRecipient())) {
if(!StringHelper.containsNonWhitespace(emailAddress)) {
emailAddress = recipient.getRecipient().getUser().getProperty(UserConstants.EMAIL, null);
}
try {
Address address = createAddress(emailAddress);
if(address != null) {
addressList.add(address);
return true;
} else {
result.addFailedIdentites(recipient.getRecipient());
if(error) {
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
}
}
} catch (AddressException e) {
result.addFailedIdentites(recipient.getRecipient());
if(error) {
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
}
}
}
}
return false;
}
private Address createAddress(Identity recipient, MailerResult result, boolean error) {
if(recipient != null) {
if(recipient.getStatus() == Identity.STATUS_LOGIN_DENIED) {
result.addFailedIdentites(recipient);
} else {
String emailAddress = recipient.getUser().getProperty(UserConstants.EMAIL, null);
Address address;
try {
address = createAddress(emailAddress);
if(address == null) {
result.addFailedIdentites(recipient);
if(error) {
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
}
}
return address;
} catch (AddressException e) {
result.addFailedIdentites(recipient);
if(error) {
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
}
}
}
}
return null;
}
private Address createFromAddress(Identity recipient, MailerResult result) {
if(recipient != null) {
String emailAddress = recipient.getUser().getProperty(UserConstants.EMAIL, null);
String name = recipient.getUser().getProperty(UserConstants.FIRSTNAME, null) + " " + recipient.getUser().getProperty(UserConstants.LASTNAME, null);
Address address;
try {
address = createAddressWithName(emailAddress, name);
if(address == null) {
result.addFailedIdentites(recipient);
result.setReturnCode(MailerResult.SENDER_ADDRESS_ERROR);
}
return address;
} catch (AddressException e) {
result.addFailedIdentites(recipient);
result.setReturnCode(MailerResult.SENDER_ADDRESS_ERROR);
} catch (UnsupportedEncodingException e) {
result.addFailedIdentites(recipient);
result.setReturnCode(MailerResult.SENDER_ADDRESS_ERROR);
}
}
return null;
}
private void sendRealMessage(Address from, List<Address> toList, List<Address> ccList, List<Address> bccList, String subject, String body,
List<File> attachments, MailerResult result) {
Address[] tos = null;
if(toList != null && !toList.isEmpty()) {
tos = new Address[toList.size()];
tos = toList.toArray(tos);
}
Address[] ccs = null;
if(ccList != null && !ccList.isEmpty()) {
ccs = new Address[ccList.size()];
ccs = ccList.toArray(ccs);
}
Address[] bccs = null;
if(bccList != null && !bccList.isEmpty()) {
bccs = new Address[bccList.size()];
bccs = bccList.toArray(bccs);
}
MimeMessage msg = createMimeMessage(from, tos, ccs, bccs, subject, body, attachments, result);
sendMessage(msg, result);
}
private MimeMessage createForwardMimeMessage(Address from, Address to, String subject, String body,
List<DBMailAttachment> attachments, MailerResult result) {
try {
MimeMessage msg = createMessage(subject, from);
if(to != null) {
msg.addRecipient(RecipientType.TO, to);
}
if (attachments != null && !attachments.isEmpty()) {
// with attachment use multipart message
Multipart multipart = new MimeMultipart();
// 1) add body part
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(body);
multipart.addBodyPart(messageBodyPart);
// 2) add attachments
for (DBMailAttachment attachment : attachments) {
// abort if attachment does not exist
if (attachment == null || attachment.getSize() <= 0) {
result.setReturnCode(MailerResult.ATTACHMENT_INVALID);
log.error("Tried to send mail wit attachment that does not exist::"
+ (attachment == null ? null : attachment.getName()), null);
return msg;
}
messageBodyPart = new MimeBodyPart();
VFSLeaf data = getAttachmentDatas(attachment);
DataSource source = new VFSDataSource(attachment.getName(), attachment.getMimetype(), data);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(attachment.getName());
multipart.addBodyPart(messageBodyPart);
}
// Put parts in message
msg.setContent(multipart);
} else {
// without attachment everything is easy, just set as text
msg.setText(body, "utf-8");
}
msg.setSentDate(new Date());
msg.saveChanges();
return msg;
} catch (MessagingException | UnsupportedEncodingException e) {
log.error("", e);
return null;
}
}
/**
* Only legal way to create a MimeMessage!
*
* @see FXOLAT-74: send all mails as <fromemail> (in config) to have a valid reverse lookup and therefore pass spam protection.
*
* @param subject
* @param from
* @return
* @throws AddressException
* @throws MessagingException
* @throws UnsupportedEncodingException
*/
private MimeMessage createMessage(String subject, Address from)
throws AddressException, MessagingException, UnsupportedEncodingException {
String mailhost = WebappHelper.getMailConfig("mailhost");
String mailport = WebappHelper.getMailConfig("mailport");
String mailhostTimeout = WebappHelper.getMailConfig("mailTimeout");
boolean sslEnabled = Boolean.parseBoolean(WebappHelper.getMailConfig("sslEnabled"));
boolean sslCheckCertificate = Boolean.parseBoolean(WebappHelper.getMailConfig("sslCheckCertificate"));
boolean startTls = Boolean.parseBoolean(WebappHelper.getMailConfig("smtpStarttls"));
Authenticator smtpAuth;
if (WebappHelper.isMailHostAuthenticationEnabled()) {
String smtpUser = WebappHelper.getMailConfig("smtpUser");
String smtpPwd = WebappHelper.getMailConfig("smtpPwd");
smtpAuth = new MailerSMTPAuthenticator(smtpUser, smtpPwd);
} else {
smtpAuth = null;
}
Properties p = new Properties();
p.put("mail.smtp.host", mailhost);
if(StringHelper.containsNonWhitespace(mailport)) {
p.put("mail.smtp.port", mailport);
}
p.put("mail.smtp.timeout", mailhostTimeout);
p.put("mail.smtp.connectiontimeout", mailhostTimeout);
p.put("mail.smtp.ssl.enable", sslEnabled);
p.put("mail.smtp.ssl.checkserveridentity", sslCheckCertificate);
if(startTls) {
p.put("mail.smtp.starttls.enable", "true");
p.put("mail.smtp.ssl.trust", mailhost);
}
p.put("mail.smtp.sendpartial", Boolean.TRUE);
Session mailSession;
if (smtpAuth == null) {
mailSession = javax.mail.Session.getInstance(p);
} else {
// use smtp authentication from configuration
p.put("mail.smtp.auth", "true");
mailSession = Session.getDefaultInstance(p, smtpAuth);
}
if (log.isDebug()) {
// enable mail session debugging on console
mailSession.setDebug(true);
}
MimeMessage msg = new MimeMessage(mailSession);
String platformFrom = WebappHelper.getMailConfig("mailFrom");
String platformName = WebappHelper.getMailConfig("mailFromName");
Address viewableFrom = createAddressWithName(platformFrom, platformName);
msg.setFrom(viewableFrom);
msg.setSubject(subject, "utf-8");
// reply to can only be an address without name (at least for postfix!), see FXOLAT-312
Address convertedFrom = getRawEmailFromAddress(from);
msg.setReplyTo(new Address[] { convertedFrom });
return msg;
}
// converts an address "bla bli <bla@bli.ch>" => "bla@bli.ch"
private InternetAddress getRawEmailFromAddress(Address address) throws AddressException {
if(address == null) {
throw new AddressException("Address cannot be null");
}
InternetAddress fromAddress = new InternetAddress(address.toString());
String fromPlainAddress = fromAddress.getAddress();
return new InternetAddress(fromPlainAddress);
}
@Override
public MimeMessage createMimeMessage(Address from, Address[] tos, Address[] ccs, Address[] bccs, String subject, String body,
List<File> attachments, MailerResult result) {
try {
MimeMessage msg = createMessage(subject, from);
if(tos != null && tos.length > 0) {
msg.addRecipients(RecipientType.TO, tos);
}
if(ccs != null && ccs.length > 0) {
msg.addRecipients(RecipientType.CC, ccs);
}
if(bccs != null && bccs.length > 0) {
msg.addRecipients(RecipientType.BCC, bccs);
}
if (attachments != null && !attachments.isEmpty()) {
// with attachment use multipart message
Multipart multipart = new MimeMultipart("mixed");
// 1) add body part
if(StringHelper.isHtml(body)) {
Multipart alternativePart = createMultipartAlternative(body);
MimeBodyPart wrap = new MimeBodyPart();
wrap.setContent(alternativePart);
multipart.addBodyPart(wrap);
} else {
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(body);
multipart.addBodyPart(messageBodyPart);
}
// 2) add attachments
for (File attachmentFile : attachments) {
// abort if attachment does not exist
if (attachmentFile == null || !attachmentFile.exists()) {
result.setReturnCode(MailerResult.ATTACHMENT_INVALID);
log.error("Tried to send mail wit attachment that does not exist::"
+ (attachmentFile == null ? null : attachmentFile.getAbsolutePath()), null);
return msg;
}
BodyPart filePart = new MimeBodyPart();
DataSource source = new FileDataSource(attachmentFile);
filePart.setDataHandler(new DataHandler(source));
filePart.setFileName(attachmentFile.getName());
multipart.addBodyPart(filePart);
}
// Put parts in message
msg.setContent(multipart);
} else {
// without attachment everything is easy, just set as text
if(StringHelper.isHtml(body)) {
msg.setContent(createMultipartAlternative(body));
} else {
msg.setText(body, "utf-8");
}
}
msg.setSentDate(new Date());
msg.saveChanges();
return msg;
} catch (AddressException e) {
result.setReturnCode(MailerResult.SENDER_ADDRESS_ERROR);
log.error("", e);
return null;
} catch (MessagingException e) {
result.setReturnCode(MailerResult.SEND_GENERAL_ERROR);
log.error("", e);
return null;
} catch (UnsupportedEncodingException e) {
result.setReturnCode(MailerResult.SENDER_ADDRESS_ERROR);
log.error("", e);
return null;
}
}
private Multipart createMultipartAlternative(String text)
throws MessagingException {
String pureText = new NekoHTMLFilter().filter(text, true);
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(pureText, "utf-8");
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setText(text, "utf-8", "html");
Multipart multipart = new MimeMultipart("alternative");
multipart.addBodyPart(textPart);
multipart.addBodyPart(htmlPart);
return multipart;
}
@Override
public void sendMessage(MimeMessage msg, MailerResult result) {
String smtpFrom = WebappHelper.getMailConfig("smtpFrom");
if(StringHelper.containsNonWhitespace(smtpFrom)) {
try {
SMTPMessage smtpMsg = new SMTPMessage(msg);
smtpMsg.setEnvelopeFrom(smtpFrom);
msg = smtpMsg;
} catch (MessagingException e) {
log.error("", e);
}
}
try{
if(Settings.isJUnitTest()) {
//we want not send really e-mails
} else if (mailModule.isMailHostEnabled() && result.getReturnCode() == MailerResult.OK) {
// now send the mail
if(Settings.isDebuging()) {
logMessage(msg);
}
Transport.send(msg);
} else if(Settings.isDebuging() && result.getReturnCode() == MailerResult.OK) {
logMessage(msg);
} else {
result.setReturnCode(MailerResult.MAILHOST_UNDEFINED);
}
} catch(SendFailedException e) {
result.setReturnCode(MailerResult.RECIPIENT_ADDRESS_ERROR);
result.addInvalidAddresses(e.getInvalidAddresses());
result.addInvalidAddresses(e.getValidUnsentAddresses());
log.warn("Could not send mail", e);
} catch (MessagingException e) {
result.setReturnCode(MailerResult.SEND_GENERAL_ERROR);
log.warn("Could not send mail", e);
}
}
private void logMessage(MimeMessage msg) throws MessagingException {
try {
log.info("E-mail send: " + msg.getSubject());
logRecipients(msg, RecipientType.TO);
logRecipients(msg, RecipientType.BCC);
logRecipients(msg, RecipientType.CC);
log.info("Content : " + msg.getContent());
//File file = new File("/HotCoffee/tmp/mail_" + CodeHelper.getForeverUniqueID() + ".msg");
//OutputStream os = new FileOutputStream(file);
//msg.writeTo(os);
//IOUtils.closeQuietly(os);
} catch (IOException e) {
log.error("", e);
}
}
private void logRecipients(MimeMessage msg, RecipientType type) throws MessagingException {
Address[] recipients = msg.getRecipients(type);
if(recipients != null && recipients.length > 0) {
StringBuilder sb = new StringBuilder();
for(Address recipient:recipients) {
if(sb.length() > 0) sb.append(", ");
sb.append(recipient.toString());
}
log.info(type + " : " + sb);
}
}
public static class SendMail implements Runnable {
private final MailBundle[] bundles;
public SendMail(MailBundle[] bundles) {
this.bundles = bundles;
}
@Override
public void run() {
CoreSpringFactory.getImpl(MailManager.class).sendMessage(bundles);
}
}
private static class VFSDataSource implements DataSource {
private final String name;
private final String contentType;
private final VFSLeaf file;
public VFSDataSource(String name, String contentType, VFSLeaf file) {
this.name = name;
this.contentType = contentType;
this.file = file;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public InputStream getInputStream() throws IOException {
return file.getInputStream();
}
@Override
public String getName() {
return name;
}
@Override
public OutputStream getOutputStream() throws IOException {
return null;
}
}
}