/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import nl.strohalm.cyclos.entities.members.Element; import nl.strohalm.cyclos.entities.members.Member; import nl.strohalm.cyclos.entities.members.Operator; import nl.strohalm.cyclos.entities.members.PendingEmailChange; import nl.strohalm.cyclos.entities.members.PendingMember; import nl.strohalm.cyclos.entities.members.messages.MessageCategory; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.entities.settings.MailSettings; import nl.strohalm.cyclos.entities.settings.MailTranslation; import nl.strohalm.cyclos.entities.settings.MessageSettings; import nl.strohalm.cyclos.exceptions.MailSendingException; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData; import nl.strohalm.cyclos.utils.transaction.TransactionCommitListener; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; /** * Handles mail sending * @author luis */ public class MailHandler implements InitializingBean, DisposableBean { private class MailParameters { private String subject; private InternetAddress replyTo; private InternetAddress to; private String body; private boolean isHTML; } private class SenderThreads extends WorkerThreads<MailParameters> { public SenderThreads(final String name, final int threadCount) { super(name, threadCount); } @Override protected void process(final MailParameters params) { doSend(params.subject, params.replyTo, params.to, params.body, params.isHTML, true); } } private SettingsServiceLocal settingsService; private LinkGenerator linkGenerator; private MessageResolver messageResolver = new MessageResolver.NoOpMessageResolver(); private SenderThreads senderThreads; private int maxThreads = 5; @Override public void afterPropertiesSet() throws Exception { senderThreads = new SenderThreads("Cyclos mail sender", maxThreads); } @Override public void destroy() throws Exception { if (senderThreads != null) { senderThreads.interrupt(); senderThreads = null; } } /** * Returns the mail address for the given Element */ public InternetAddress getInternetAddress(final Element element) { if (element == null) { return null; } return getInternetAddress(element.getName(), element.getEmail()); } /** * Returns the mail address for the given PendingMember */ public InternetAddress getInternetAddress(final PendingMember pendingMember) { if (pendingMember == null) { return null; } return getInternetAddress(pendingMember.getName(), pendingMember.getEmail()); } /** * Returns the mail address for the given Element */ public InternetAddress getInternetAddress(String name, final String email) { if (StringUtils.isEmpty(email)) { return null; } final LocalSettings localSettings = settingsService.getLocalSettings(); if (StringUtils.isEmpty(name)) { name = email; } try { return new InternetAddress(email, name, localSettings.getCharset()); } catch (final UnsupportedEncodingException e) { // Shouldn't happen!!! return null; } } /** * Returns the mail address */ public InternetAddress getMailAddress(final String mail) { final LocalSettings localSettings = settingsService.getLocalSettings(); try { return new InternetAddress(mail, mail, localSettings.getCharset()); } catch (final UnsupportedEncodingException e) { // Shouldn't happen!!! return null; } } /** * Returns the mail address for the given element, but returns null when a member hides his mail */ public InternetAddress getReplyAddress(Element element) { // When an operator, check his member if (element instanceof Operator) { element = ((Operator) element).getMember(); } // Check whether the mail is hidden if (element instanceof Member) { final Member member = (Member) element; if (member.isHideEmail()) { return null; } } return getInternetAddress(element); } /** * Returns the mail address for system */ public InternetAddress getSystemAddress() { final LocalSettings localSettings = settingsService.getLocalSettings(); final MailSettings mailSettings = settingsService.getMailSettings(); try { return new InternetAddress(mailSettings.getFromMail(), localSettings.getApplicationName(), localSettings.getCharset()); } catch (final UnsupportedEncodingException e) { // Shouldn't happen!!! return null; } } /** * Process the body of a message that's being sent by mail, appending the from member, the category and a suffix */ public String processBody(final Element owner, final String body, final Member member, final MessageCategory category, final boolean isHtml) { final LocalSettings localSettings = settingsService.getLocalSettings(); final String lineSep = isHtml ? "<br>" : "\n"; final StringBuilder sb = new StringBuilder(); // When there is a member, it's from member (obviously). When there's a category, from the administration. // When neither, it's automatically generated from system boolean appendExtraLine = false; if (member != null || category != null) { sb.append(processLabel("message.from", isHtml)); if (member == null) { sb.append(localSettings.getApplicationUsername()); } else { sb.append(member.getName()).append(" (").append(member.getUsername()).append(")"); } sb.append(lineSep); appendExtraLine = true; } // Append the category, if any if (category != null) { sb.append(processLabel("message.category", isHtml)); sb.append(category.getName()); sb.append(lineSep); appendExtraLine = true; } if (appendExtraLine) { sb.append(lineSep); } sb.append(body); sb.append(lineSep); sb.append(lineSep); final MessageSettings messageSettings = settingsService.getMessageSettings(); // Append and process the suffix String suffix; if (isHtml) { suffix = messageSettings.getMessageMailSuffixHtml(); } else { suffix = messageSettings.getMessageMailSuffixPlain(); } final Map<String, String> variables = new HashMap<String, String>(); variables.put("link", getRootUrl(owner, isHtml, true)); variables.put("system_name", localSettings.getApplicationName()); sb.append(MessageProcessingHelper.processVariables(suffix, variables)); return sb.toString(); } /** * Process the body of a message that's being sent by mail, appending the prefix */ public String processSubject(final Element owner, final String subject) { final LocalSettings localSettings = settingsService.getLocalSettings(); final MessageSettings messageSettings = settingsService.getMessageSettings(); String prefix = StringUtils.trimToEmpty(messageSettings.getMessageMailSubjectPrefix()); final Map<String, String> variables = new HashMap<String, String>(); variables.put("link", getRootUrl(owner, false, true)); variables.put("system_name", localSettings.getApplicationName()); prefix = MessageProcessingHelper.processVariables(prefix, variables); return StringUtils.trimToEmpty(prefix + " " + subject); } /** * Sends an arbitrary mail message */ public void send(final String subject, final InternetAddress replyTo, final InternetAddress to, final String body, final boolean isHTML) { doSend(subject, replyTo, to, body, isHTML, false); } public void sendActivation(final boolean threaded, final Member member, final String password) { final LocalSettings localSettings = settingsService.getLocalSettings(); final Map<String, Object> variableValues = member.getVariableValues(localSettings); variableValues.put("password", password); variableValues.put("system_name", localSettings.getApplicationName()); variableValues.put("link", getRootUrl(member, true, false)); variableValues.put("url", getRootUrl(member, true, true)); final MailTranslation mailTranslation = settingsService.getMailTranslation(); final String subject = mailTranslation.getActivationSubject(); String body; if (password == null) { body = mailTranslation.getActivationMessageWithoutPassword(); } else { body = mailTranslation.getActivationMessageWithPassword(); } sendInternal(threaded, getInternetAddress(member), variableValues, subject, body); } /** * Sends an email only after the current transaction is committed */ public void sendAfterTransactionCommit(final String subject, final InternetAddress replyTo, final InternetAddress to, final String body, final boolean isHTML) { if (senderThreads == null) { return; } CurrentTransactionData.addTransactionCommitListener(new TransactionCommitListener() { @Override public void onTransactionCommit() { final MailParameters params = new MailParameters(); params.subject = subject; params.replyTo = replyTo; params.to = to; params.body = body; params.isHTML = isHTML; senderThreads.enqueue(params); } }); } public void sendEmailChange(final PendingEmailChange pendingEmailChange) { final LocalSettings localSettings = settingsService.getLocalSettings(); final Map<String, Object> variableValues = pendingEmailChange.getVariableValues(localSettings); variableValues.put("system_name", localSettings.getApplicationName()); variableValues.put("link", linkGenerator.generateLinkForMailChangeValidation(pendingEmailChange)); variableValues.put("url", linkGenerator.getMailChangeValidationUrl(pendingEmailChange)); final MailTranslation mailTranslation = settingsService.getMailTranslation(); final String subject = mailTranslation.getMailChangeValidationSubject(); final String body = mailTranslation.getMailChangeValidationMessage(); Member member = pendingEmailChange.getMember(); InternetAddress internetAddress = getInternetAddress(member.getName(), pendingEmailChange.getNewEmail()); sendInternal(false, internetAddress, variableValues, subject, body); } public void sendEmailValidation(final PendingMember pendingMember) { final LocalSettings localSettings = settingsService.getLocalSettings(); final Map<String, Object> variableValues = pendingMember.getVariableValues(localSettings); variableValues.put("system_name", localSettings.getApplicationName()); final String key = pendingMember.getValidationKey(); variableValues.put("link", linkGenerator.generateLinkForMailValidation(pendingMember.getMemberGroup(), key)); variableValues.put("url", linkGenerator.getMailValidationUrl(pendingMember.getMemberGroup(), key)); final MailTranslation mailTranslation = settingsService.getMailTranslation(); final String subject = mailTranslation.getMailValidationSubject(); final String body = mailTranslation.getMailValidationMessage(); sendInternal(false, getInternetAddress(pendingMember), variableValues, subject, body); } public void sendInvitation(final Element fromElement, final String toMail) { final LocalSettings localSettings = settingsService.getLocalSettings(); final Map<String, Object> variableValues = fromElement.getVariableValues(localSettings); variableValues.put("system_name", localSettings.getApplicationName()); variableValues.put("link", getRootUrl(fromElement, true, true)); final MailTranslation mailTranslation = settingsService.getMailTranslation(); final String subject = mailTranslation.getInvitationSubject(); final String body = mailTranslation.getInvitationMessage(); sendInternal(false, getMailAddress(toMail), variableValues, subject, body); } public void sendResetPassword(final Member member, final String password) { final LocalSettings localSettings = settingsService.getLocalSettings(); final Map<String, Object> variableValues = member.getVariableValues(localSettings); variableValues.put("password", password); variableValues.put("system_name", localSettings.getApplicationName()); variableValues.put("link", getRootUrl(member, true, true)); final MailTranslation mailTranslation = settingsService.getMailTranslation(); final String subject = mailTranslation.getResetPasswordSubject(); final String body = mailTranslation.getResetPasswordMessage(); sendInternal(false, getInternetAddress(member), variableValues, subject, body); } public void setLinkGenerator(final LinkGenerator linkGenerator) { this.linkGenerator = linkGenerator; } public void setMaxThreads(final int maxThreads) { this.maxThreads = maxThreads; } public void setMessageResolver(final MessageResolver messageResolver) { this.messageResolver = messageResolver; } public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) { this.settingsService = settingsService; } private void doSend(final String subject, final InternetAddress replyTo, final InternetAddress to, final String body, final boolean isHTML, final boolean throwException) { if (to == null || StringUtils.isEmpty(to.getAddress())) { return; } final LocalSettings localSettings = settingsService.getLocalSettings(); final MailSettings mailSettings = settingsService.getMailSettings(); final JavaMailSender mailSender = mailSettings.getMailSender(); final MimeMessage message = mailSender.createMimeMessage(); final MimeMessageHelper helper = new MimeMessageHelper(message, localSettings.getCharset()); try { helper.setFrom(getSystemAddress()); if (replyTo != null) { helper.setReplyTo(replyTo); } helper.setTo(to); helper.setSubject(subject); helper.setText(body, isHTML); mailSender.send(message); } catch (final MessagingException e) { if (throwException) { throw new MailSendingException(subject, e); } // Store the current Exception CurrentTransactionData.setMailError(new MailSendingException(subject, e)); } catch (final MailException e) { if (throwException) { throw new MailSendingException(subject, e); } CurrentTransactionData.setMailError(new MailSendingException(subject, e)); } } private String getRootUrl(final Element element, final boolean isHtml, final boolean useUrlAsLink) { if (linkGenerator == null) { return ""; } final String rootUrl = linkGenerator.getRootUrl(element); if (isHtml) { if (useUrlAsLink) { return "<a href='" + rootUrl + "'>" + rootUrl + "</a>"; } else { return linkGenerator.generateForApplicationRoot(element); } } else { return rootUrl; } } private String processLabel(final String key, final boolean isHtml) { String prefix = ""; String suffix = ""; if (isHtml) { prefix = "<b>"; suffix = "</b>"; } return prefix + messageResolver.message(key) + ":" + suffix + " "; } private void sendInternal(final boolean threaded, final InternetAddress to, final Map<String, Object> variables, final String subject, final String body) { final String processedSubject = MessageProcessingHelper.processVariables(subject, variables); final String processedBody = MessageProcessingHelper.processVariables(body, variables); if (threaded) { sendAfterTransactionCommit(processedSubject, null, to, processedBody, true); } else { send(processedSubject, null, to, processedBody, true); } } }