James Moger
2014-02-28 d7c5a62f95c06839bda044d4bed11117219ad1a7
Improve notification api by introducing the Mailing model
1 files added
7 files modified
498 ■■■■ changed files
src/main/java/com/gitblit/FederationClient.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/GitblitAuthority.java 29 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/GitblitManager.java 20 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/INotificationManager.java 38 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/NotificationManager.java 123 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/Mailing.java 111 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/service/MailService.java 157 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/MailTest.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationClient.java
@@ -32,6 +32,7 @@
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;
@@ -178,23 +179,11 @@
        }
        @Override
        public void sendMail(String subject, String message, String... toAddresses) {
        }
        @Override
        public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
        }
        @Override
        public void sendHtmlMail(String subject, String message, String... toAddresses) {
        }
        @Override
        public void sendHtmlMail(String from, String subject, String message, Collection<String> toAddresses) {
        }
        @Override
        public void sendHtmlMail(String from, String subject, String message, String... toAddresses) {
        public void send(Mailing mailing) {
        }
    }
}
src/main/java/com/gitblit/authority/GitblitAuthority.java
@@ -43,7 +43,6 @@
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.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.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 @@
        // 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);
                mailing.content = body;
                mailing.addAttachment(zip);
                // 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);
                Message message = mail.createMessage(mailing);
                mail.sendNow(message);
                return true;
src/main/java/com/gitblit/manager/GitblitManager.java
@@ -50,6 +50,7 @@
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;
@@ -584,28 +585,13 @@
    }
    @Override
    public void sendMail(String subject, String message, String... toAddresses) {
        notificationManager.sendMail(subject, message, toAddresses);
    }
    @Override
    public void sendHtmlMail(String subject, String message, Collection<String> 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<String> 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);
    }
    /*
src/main/java/com/gitblit/manager/INotificationManager.java
@@ -17,6 +17,8 @@
import java.util.Collection;
import com.gitblit.models.Mailing;
public interface INotificationManager extends IManager {
    /**
@@ -43,44 +45,14 @@
     * @param message
     * @param toAddresses
     */
    void sendMail(String subject, String message, String... toAddresses);
    /**
     * Notify users by email of something.
     *
     * @param subject
     * @param message
     * @param toAddresses
     */
    void sendHtmlMail(String subject, String message, Collection<String> toAddresses);
    /**
     * Notify users by email of something.
     *
     * @param subject
     * @param message
     * @param toAddresses
     * @param mailing
     * @return the mail message object
     */
    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<String> toAddresses);
    /**
     * Notify users by email of something.
     *
     * @param from
     * @param subject
     * @param message
     * @param toAddresses
     */
    void sendHtmlMail(String from, String subject, String message, String... toAddresses);
    void send(Mailing mailing);
}
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 @@
    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 @@
     */
    @Override
    public void sendMailToAdministrators(String subject, String message) {
        List<String> 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 @@
     */
    @Override
    public void sendMail(String subject, String message, Collection<String> 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 @@
     */
    @Override
    public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
        this.sendHtmlMail(null, subject, message, toAddresses.toArray(new String[0]));
        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, Collection<String> 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);
    }
    /**
     * Notify users by email of something.
     *
     * @param from
     * @param subject
     * @param message
     * @param toAddresses
     */
    @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);
        }
    }
src/main/java/com/gitblit/models/Mailing.java
New file
@@ -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<String> toAddresses;
    public final Set<String> ccAddresses;
    public final List<File> 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<String>();
        this.ccAddresses = new TreeSet<String>();
        this.attachments = new ArrayList<File>();
    }
    public boolean hasRecipients() {
        return toAddresses.size() > 0;
    }
    public void setRecipients(String... addrs) {
        setRecipients(Arrays.asList(addrs));
    }
    public void setRecipients(Collection<String> 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<String> 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;
    }
}
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 @@
    /**
     * Create a message.
     *
     * @param toAddresses
     * @param mailing
     * @return a message
     */
    public Message createMessage(String... toAddresses) {
        return createMessage(null, Arrays.asList(toAddresses));
    }
    public Message createMessage(Mailing mailing) {
        if (mailing.subject == null) {
            mailing.subject = "";
        }
    /**
     * Create a message.
     *
     * @param toAddresses
     * @return a message
     */
    public Message createMessage(List<String> toAddresses) {
        return createMessage(null, toAddresses);
    }
        if (mailing.content == null) {
            mailing.content = "";
        }
    /**
     * Create a message.
     *
     * @param fromDisplayName
     * @param toAddresses
     * @return a message
     */
    public Message createMessage(String fromDisplayName, String... toAddresses) {
        return createMessage(fromDisplayName, Arrays.asList(toAddresses));
    }
    /**
     * Create a message.
     *
     * @param fromDisplayName
     * @param toAddresses
     * @return a message
     */
    public Message createMessage(String fromDisplayName, List<String> 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<String> uniques = new HashSet<String>();
            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<InternetAddress> tos = new ArrayList<InternetAddress>();
            for (String address : uniques) {
            // validate & add TO recipients
            List<InternetAddress> to = new ArrayList<InternetAddress>();
            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<InternetAddress> cc = new ArrayList<InternetAddress>();
            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<InternetAddress> bcc = new ArrayList<InternetAddress>();
                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 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);
            }
        }
    }
}
src/test/java/com/gitblit/tests/MailTest.java
@@ -21,6 +21,7 @@
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 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);