From d7c5a62f95c06839bda044d4bed11117219ad1a7 Mon Sep 17 00:00:00 2001 From: James Moger Date: Fri, 28 Feb 2014 13:49:09 -0500 Subject: [PATCH] Improve notification api by introducing the Mailing model --- .../java/com/gitblit/FederationClient.java | 15 +- .../gitblit/authority/GitblitAuthority.java | 31 +--- .../com/gitblit/manager/GitblitManager.java | 20 +-- .../gitblit/manager/INotificationManager.java | 38 +---- .../gitblit/manager/NotificationManager.java | 123 +++----------- src/main/java/com/gitblit/models/Mailing.java | 111 +++++++++++++ .../java/com/gitblit/service/MailService.java | 157 ++++++++++++------ src/test/java/com/gitblit/tests/MailTest.java | 5 +- 8 files changed, 268 insertions(+), 232 deletions(-) create mode 100644 src/main/java/com/gitblit/models/Mailing.java diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java index 4f4b00bc..628a05dc 100644 --- a/src/main/java/com/gitblit/FederationClient.java +++ b/src/main/java/com/gitblit/FederationClient.java @@ -32,6 +32,7 @@ import com.gitblit.manager.RepositoryManager; import com.gitblit.manager.RuntimeManager; import com.gitblit.manager.UserManager; import com.gitblit.models.FederationModel; +import com.gitblit.models.Mailing; import com.gitblit.service.FederationPullService; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.StringUtils; @@ -177,24 +178,12 @@ public class FederationClient { public void sendMail(String subject, String message, Collection toAddresses) { } - @Override - public void sendMail(String subject, String message, String... toAddresses) { - } - @Override public void sendHtmlMail(String subject, String message, Collection toAddresses) { } @Override - public void sendHtmlMail(String subject, String message, String... toAddresses) { - } - - @Override - public void sendHtmlMail(String from, String subject, String message, Collection toAddresses) { - } - - @Override - public void sendHtmlMail(String from, String subject, String message, String... toAddresses) { + public void send(Mailing mailing) { } } } diff --git a/src/main/java/com/gitblit/authority/GitblitAuthority.java b/src/main/java/com/gitblit/authority/GitblitAuthority.java index 51626e64..5f4a7e7f 100644 --- a/src/main/java/com/gitblit/authority/GitblitAuthority.java +++ b/src/main/java/com/gitblit/authority/GitblitAuthority.java @@ -43,7 +43,6 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -51,12 +50,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.activation.DataHandler; -import javax.activation.FileDataSource; import javax.mail.Message; -import javax.mail.Multipart; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMultipart; import javax.swing.ImageIcon; import javax.swing.InputVerifier; import javax.swing.JButton; @@ -93,6 +87,7 @@ import com.gitblit.IUserService; import com.gitblit.Keys; import com.gitblit.client.HeaderPanel; import com.gitblit.client.Translation; +import com.gitblit.models.Mailing; import com.gitblit.models.UserModel; import com.gitblit.service.MailService; import com.gitblit.utils.ArrayUtils; @@ -854,27 +849,17 @@ public class GitblitAuthority extends JFrame implements X509Log { // send email try { if (mail.isReady()) { - Message message = mail.createMessage(Arrays.asList(user.emailAddress)); - message.setSubject("Your Gitblit client certificate for " + metadata.serverHostname); - - // body of email + Mailing mailing = Mailing.newPlain(); + mailing.subject = "Your Gitblit client certificate for " + metadata.serverHostname; + mailing.setRecipients(user.emailAddress); String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata); if (StringUtils.isEmpty(body)) { body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName()); } - Multipart mp = new MimeMultipart(); - MimeBodyPart messagePart = new MimeBodyPart(); - messagePart.setText(body); - mp.addBodyPart(messagePart); - - // attach zip - MimeBodyPart filePart = new MimeBodyPart(); - FileDataSource fds = new FileDataSource(zip); - filePart.setDataHandler(new DataHandler(fds)); - filePart.setFileName(fds.getName()); - mp.addBodyPart(filePart); - - message.setContent(mp); + mailing.content = body; + mailing.addAttachment(zip); + + Message message = mail.createMessage(mailing); mail.sendNow(message); return true; diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java index 9d096ddf..08d64085 100644 --- a/src/main/java/com/gitblit/manager/GitblitManager.java +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -50,6 +50,7 @@ import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; import com.gitblit.models.ForkModel; import com.gitblit.models.GitClientApplication; +import com.gitblit.models.Mailing; import com.gitblit.models.Metric; import com.gitblit.models.ProjectModel; import com.gitblit.models.RegistrantAccessPermission; @@ -583,29 +584,14 @@ public class GitblitManager implements IGitblit { notificationManager.sendMail(subject, message, toAddresses); } - @Override - public void sendMail(String subject, String message, String... toAddresses) { - notificationManager.sendMail(subject, message, toAddresses); - } - @Override public void sendHtmlMail(String subject, String message, Collection toAddresses) { notificationManager.sendHtmlMail(subject, message, toAddresses); } @Override - public void sendHtmlMail(String subject, String message, String... toAddresses) { - notificationManager.sendHtmlMail(subject, message, toAddresses); - } - - @Override - public void sendHtmlMail(String from, String subject, String message, Collection toAddresses) { - notificationManager.sendHtmlMail(from, subject, message, toAddresses); - } - - @Override - public void sendHtmlMail(String from, String subject, String message, String... toAddresses) { - notificationManager.sendHtmlMail(from, subject, message, toAddresses); + public void send(Mailing mail) { + notificationManager.send(mail); } /* diff --git a/src/main/java/com/gitblit/manager/INotificationManager.java b/src/main/java/com/gitblit/manager/INotificationManager.java index ce5d3f71..6bee2f8e 100644 --- a/src/main/java/com/gitblit/manager/INotificationManager.java +++ b/src/main/java/com/gitblit/manager/INotificationManager.java @@ -17,6 +17,8 @@ package com.gitblit.manager; import java.util.Collection; +import com.gitblit.models.Mailing; + public interface INotificationManager extends IManager { /** @@ -36,15 +38,6 @@ public interface INotificationManager extends IManager { */ void sendMail(String subject, String message, Collection toAddresses); - /** - * Notify users by email of something. - * - * @param subject - * @param message - * @param toAddresses - */ - void sendMail(String subject, String message, String... toAddresses); - /** * Notify users by email of something. * @@ -57,30 +50,9 @@ public interface INotificationManager extends IManager { /** * Notify users by email of something. * - * @param subject - * @param message - * @param toAddresses - */ - void sendHtmlMail(String subject, String message, String... toAddresses); - - /** - * Notify users by email of something. - * - * @param from - * @param subject - * @param message - * @param toAddresses - */ - void sendHtmlMail(String from, String subject, String message, Collection toAddresses); - - /** - * Notify users by email of something. - * - * @param from - * @param subject - * @param message - * @param toAddresses + * @param mailing + * @return the mail message object */ - void sendHtmlMail(String from, String subject, String message, String... toAddresses); + void send(Mailing mailing); } \ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/NotificationManager.java b/src/main/java/com/gitblit/manager/NotificationManager.java index 22ae5517..f1558b6a 100644 --- a/src/main/java/com/gitblit/manager/NotificationManager.java +++ b/src/main/java/com/gitblit/manager/NotificationManager.java @@ -15,23 +15,19 @@ */ package com.gitblit.manager; -import java.text.MessageFormat; import java.util.Collection; -import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMultipart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; +import com.gitblit.models.Mailing; import com.gitblit.service.MailService; /** @@ -50,19 +46,19 @@ public class NotificationManager implements INotificationManager { private final IStoredSettings settings; - private final MailService mailExecutor; + private final MailService mailService; public NotificationManager(IStoredSettings settings) { this.settings = settings; - this.mailExecutor = new MailService(settings); + this.mailService = new MailService(settings); } @Override public NotificationManager start() { - if (mailExecutor.isReady()) { + if (mailService.isReady()) { int period = 2; logger.info("Mail service will process the queue every {} minutes.", period); - scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, period, TimeUnit.MINUTES); + scheduledExecutor.scheduleAtFixedRate(mailService, 1, period, TimeUnit.MINUTES); } else { logger.warn("Mail service disabled."); } @@ -83,8 +79,11 @@ public class NotificationManager implements INotificationManager { */ @Override public void sendMailToAdministrators(String subject, String message) { - List toAddresses = settings.getStrings(Keys.mail.adminAddresses); - sendMail(subject, message, toAddresses); + Mailing mail = Mailing.newPlain(); + mail.subject = subject; + mail.content = message; + mail.setRecipients(settings.getStrings(Keys.mail.adminAddresses)); + send(mail); } /** @@ -96,41 +95,11 @@ public class NotificationManager implements INotificationManager { */ @Override public void sendMail(String subject, String message, Collection toAddresses) { - this.sendMail(subject, message, toAddresses.toArray(new String[0])); - } - - /** - * Notify users by email of something. - * - * @param subject - * @param message - * @param toAddresses - */ - @Override - public void sendMail(String subject, String message, String... toAddresses) { - if (toAddresses == null || toAddresses.length == 0) { - logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject)); - return; - } - try { - Message mail = mailExecutor.createMessage(toAddresses); - if (mail != null) { - mail.setSubject(subject); - - MimeBodyPart messagePart = new MimeBodyPart(); - messagePart.setText(message, "utf-8"); - messagePart.setHeader("Content-Type", "text/plain; charset=\"utf-8\""); - messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable"); - - MimeMultipart multiPart = new MimeMultipart(); - multiPart.addBodyPart(messagePart); - mail.setContent(multiPart); - - mailExecutor.queue(mail); - } - } catch (MessagingException e) { - logger.error("Messaging error", e); - } + Mailing mail = Mailing.newPlain(); + mail.subject = subject; + mail.content = message; + mail.setRecipients(toAddresses); + send(mail); } /** @@ -142,66 +111,26 @@ public class NotificationManager implements INotificationManager { */ @Override public void sendHtmlMail(String subject, String message, Collection toAddresses) { - this.sendHtmlMail(null, subject, message, toAddresses.toArray(new String[0])); - } - - /** - * Notify users by email of something. - * - * @param from - * @param subject - * @param message - * @param toAddresses - */ - @Override - public void sendHtmlMail(String from, String subject, String message, Collection toAddresses) { - this.sendHtmlMail(from, subject, message, toAddresses.toArray(new String[0])); - } - - /** - * Notify users by email of something. - * - * @param subject - * @param message - * @param toAddresses - */ - @Override - public void sendHtmlMail(String subject, String message, String... toAddresses) { - this.sendHtmlMail(null, message, toAddresses); + Mailing mail = Mailing.newHtml(); + mail.content = message; + mail.setRecipients(toAddresses); + send(mail); } /** * Notify users by email of something. * - * @param from - * @param subject - * @param message - * @param toAddresses + * @param mailing */ @Override - public void sendHtmlMail(String from, String subject, String message, String... toAddresses) { - if (toAddresses == null || toAddresses.length == 0) { - logger.debug("Dropping message {} because there are no recipients", subject); + public void send(Mailing mailing) { + if (!mailing.hasRecipients()) { + logger.debug("Dropping message {} because there are no recipients", mailing.subject); return; } - try { - Message mail = mailExecutor.createMessage(from, toAddresses); - if (mail != null) { - mail.setSubject(subject); - - MimeBodyPart messagePart = new MimeBodyPart(); - messagePart.setText(message, "utf-8"); - messagePart.setHeader("Content-Type", "text/html; charset=\"utf-8\""); - messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable"); - - MimeMultipart multiPart = new MimeMultipart(); - multiPart.addBodyPart(messagePart); - mail.setContent(multiPart); - - mailExecutor.queue(mail); - } - } catch (MessagingException e) { - logger.error("Messaging error", e); + Message msg = mailService.createMessage(mailing); + if (msg != null) { + mailService.queue(msg); } } diff --git a/src/main/java/com/gitblit/models/Mailing.java b/src/main/java/com/gitblit/models/Mailing.java new file mode 100644 index 00000000..9c0ff906 --- /dev/null +++ b/src/main/java/com/gitblit/models/Mailing.java @@ -0,0 +1,111 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.models; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * Encapsulates an email notification. + * + * @author James Moger + * + */ +public class Mailing { + + public enum Type { + plain, html + } + + public final Type type; + public final Set toAddresses; + public final Set ccAddresses; + public final List attachments; + + public String from; + public String subject; + public String content; + public String id; + + public static Mailing newHtml() { + return new Mailing(Type.html); + } + + public static Mailing newPlain() { + return new Mailing(Type.plain); + } + + private Mailing(Type type) { + this.type = type; + this.toAddresses = new TreeSet(); + this.ccAddresses = new TreeSet(); + this.attachments = new ArrayList(); + } + + public boolean hasRecipients() { + return toAddresses.size() > 0; + } + + public void setRecipients(String... addrs) { + setRecipients(Arrays.asList(addrs)); + } + + public void setRecipients(Collection addrs) { + toAddresses.clear(); + for (String addr : addrs) { + toAddresses.add(addr.toLowerCase()); + } + cleanup(); + } + + public boolean hasCCs() { + return ccAddresses.size() > 0; + } + + public void setCCs(String... addrs) { + setCCs(Arrays.asList(addrs)); + } + + public void setCCs(Collection addrs) { + ccAddresses.clear(); + for (String addr : addrs) { + ccAddresses.add(addr.toLowerCase()); + } + cleanup(); + } + + private void cleanup() { + ccAddresses.removeAll(toAddresses); + } + + public boolean hasAttachments() { + return attachments.size() > 0; + } + + public void addAttachment(File file) { + attachments.add(file); + } + + @Override + public String toString() { + return subject + "\n\n" + content; + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/service/MailService.java b/src/main/java/com/gitblit/service/MailService.java index 54b6c911..4759d177 100644 --- a/src/main/java/com/gitblit/service/MailService.java +++ b/src/main/java/com/gitblit/service/MailService.java @@ -15,30 +15,35 @@ */ package com.gitblit.service; +import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Queue; -import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.regex.Pattern; +import javax.activation.DataHandler; +import javax.activation.FileDataSource; import javax.mail.Authenticator; import javax.mail.Message; +import javax.mail.MessagingException; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; +import com.gitblit.models.Mailing; import com.gitblit.utils.StringUtils; /** @@ -117,74 +122,108 @@ public class MailService implements Runnable { /** * Create a message. * - * @param toAddresses + * @param mailing * @return a message */ - public Message createMessage(String... toAddresses) { - return createMessage(null, Arrays.asList(toAddresses)); - } - - /** - * Create a message. - * - * @param toAddresses - * @return a message - */ - public Message createMessage(List toAddresses) { - return createMessage(null, toAddresses); - } + public Message createMessage(Mailing mailing) { + if (mailing.subject == null) { + mailing.subject = ""; + } - /** - * Create a message. - * - * @param fromDisplayName - * @param toAddresses - * @return a message - */ - public Message createMessage(String fromDisplayName, String... toAddresses) { - return createMessage(fromDisplayName, Arrays.asList(toAddresses)); - } + if (mailing.content == null) { + mailing.content = ""; + } - /** - * Create a message. - * - * @param fromDisplayName - * @param toAddresses - * @return a message - */ - public Message createMessage(String fromDisplayName, List toAddresses) { - MimeMessage message = new MimeMessage(session); + Message message = new MailMessageImpl(session, mailing.id); try { String fromAddress = settings.getString(Keys.mail.fromAddress, null); if (StringUtils.isEmpty(fromAddress)) { fromAddress = "gitblit@gitblit.com"; } - InternetAddress from = new InternetAddress(fromAddress, fromDisplayName == null ? "Gitblit" : fromDisplayName); + InternetAddress from = new InternetAddress(fromAddress, mailing.from == null ? "Gitblit" : mailing.from); message.setFrom(from); - // determine unique set of addresses - Set uniques = new HashSet(); - for (String address : toAddresses) { - uniques.add(address.toLowerCase()); - } - Pattern validEmail = Pattern .compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); - List tos = new ArrayList(); - for (String address : uniques) { + + // validate & add TO recipients + List to = new ArrayList(); + for (String address : mailing.toAddresses) { if (StringUtils.isEmpty(address)) { continue; } if (validEmail.matcher(address).find()) { try { - tos.add(new InternetAddress(address)); + to.add(new InternetAddress(address)); } catch (Throwable t) { } } } - message.setRecipients(Message.RecipientType.BCC, - tos.toArray(new InternetAddress[tos.size()])); + + // validate & add CC recipients + List cc = new ArrayList(); + for (String address : mailing.ccAddresses) { + if (StringUtils.isEmpty(address)) { + continue; + } + if (validEmail.matcher(address).find()) { + try { + cc.add(new InternetAddress(address)); + } catch (Throwable t) { + } + } + } + + if (settings.getBoolean(Keys.web.showEmailAddresses, true)) { + // full disclosure of recipients + if (to.size() > 0) { + message.setRecipients(Message.RecipientType.TO, + to.toArray(new InternetAddress[to.size()])); + } + if (cc.size() > 0) { + message.setRecipients(Message.RecipientType.CC, + cc.toArray(new InternetAddress[cc.size()])); + } + } else { + // everyone is bcc'd + List bcc = new ArrayList(); + bcc.addAll(to); + bcc.addAll(cc); + message.setRecipients(Message.RecipientType.BCC, + bcc.toArray(new InternetAddress[bcc.size()])); + } + message.setSentDate(new Date()); + message.setSubject(mailing.subject); + + MimeBodyPart messagePart = new MimeBodyPart(); + messagePart.setText(mailing.content, "utf-8"); + //messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable"); + + if (Mailing.Type.html == mailing.type) { + messagePart.setHeader("Content-Type", "text/html; charset=\"utf-8\""); + } else { + messagePart.setHeader("Content-Type", "text/plain; charset=\"utf-8\""); + } + + MimeMultipart multiPart = new MimeMultipart(); + multiPart.addBodyPart(messagePart); + + // handle attachments + if (mailing.hasAttachments()) { + for (File file : mailing.attachments) { + if (file.exists()) { + MimeBodyPart filePart = new MimeBodyPart(); + FileDataSource fds = new FileDataSource(file); + filePart.setDataHandler(new DataHandler(fds)); + filePart.setFileName(fds.getName()); + multiPart.addBodyPart(filePart); + } + } + } + + message.setContent(multiPart); + } catch (Exception e) { logger.error("Failed to properly create message", e); } @@ -247,4 +286,26 @@ public class MailService implements Runnable { public void sendNow(Message message) throws Exception { Transport.send(message); } + + private static class MailMessageImpl extends MimeMessage { + + final String id; + + MailMessageImpl(Session session, String id) { + super(session); + this.id = id; + } + + @Override + protected void updateMessageID() throws MessagingException { + if (!StringUtils.isEmpty(id)) { + String hostname = "gitblit.com"; + String refid = "<" + id + "@" + hostname + ">"; + String mid = "<" + UUID.randomUUID().toString() + "@" + hostname + ">"; + setHeader("References", refid); + setHeader("In-Reply-To", refid); + setHeader("Message-Id", mid); + } + } + } } diff --git a/src/test/java/com/gitblit/tests/MailTest.java b/src/test/java/com/gitblit/tests/MailTest.java index df09ca59..0224e84e 100644 --- a/src/test/java/com/gitblit/tests/MailTest.java +++ b/src/test/java/com/gitblit/tests/MailTest.java @@ -21,6 +21,7 @@ import org.junit.Test; import com.gitblit.FileSettings; import com.gitblit.Keys; +import com.gitblit.models.Mailing; import com.gitblit.service.MailService; public class MailTest extends GitblitUnitTest { @@ -29,7 +30,9 @@ public class MailTest extends GitblitUnitTest { public void testSendMail() throws Exception { FileSettings settings = new FileSettings("mailtest.properties"); MailService mail = new MailService(settings); - Message message = mail.createMessage(settings.getStrings(Keys.mail.adminAddresses)); + Mailing mailing = Mailing.newPlain(); + mailing.setRecipients(settings.getStrings(Keys.mail.adminAddresses)); + Message message = mail.createMessage(mailing); message.setSubject("Test"); message.setText("Lägger till andra stycket i ny fil. UTF-8 encoded"); mail.queue(message); -- 2.39.5