Rafael Cavazin
2013-07-21 f66e89662c091e082bd1d2feb6ac91513ccff273
src/main/java/com/gitblit/MailExecutor.java
New file
@@ -0,0 +1,226 @@
/*
 * Copyright 2011 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;
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.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Pattern;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.utils.StringUtils;
/**
 * The mail executor handles sending email messages asynchronously from queue.
 *
 * @author James Moger
 *
 */
public class MailExecutor implements Runnable {
   private final Logger logger = LoggerFactory.getLogger(MailExecutor.class);
   private final Queue<Message> queue = new ConcurrentLinkedQueue<Message>();
   private final Session session;
   private final IStoredSettings settings;
   public MailExecutor(IStoredSettings settings) {
      this.settings = settings;
      final String mailUser = settings.getString(Keys.mail.username, null);
      final String mailPassword = settings.getString(Keys.mail.password, null);
      final boolean smtps = settings.getBoolean(Keys.mail.smtps, false);
      boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword);
      String server = settings.getString(Keys.mail.server, "");
      if (StringUtils.isEmpty(server)) {
         session = null;
         return;
      }
      int port = settings.getInteger(Keys.mail.port, 25);
      boolean isGMail = false;
      if (server.equals("smtp.gmail.com")) {
         port = 465;
         isGMail = true;
      }
      Properties props = new Properties();
      props.setProperty("mail.smtp.host", server);
      props.setProperty("mail.smtp.port", String.valueOf(port));
      props.setProperty("mail.smtp.auth", String.valueOf(authenticate));
      props.setProperty("mail.smtp.auths", String.valueOf(authenticate));
      if (isGMail || smtps) {
         props.setProperty("mail.smtp.starttls.enable", "true");
         props.put("mail.smtp.socketFactory.port", String.valueOf(port));
         props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
         props.put("mail.smtp.socketFactory.fallback", "false");
      }
      if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
         // SMTP requires authentication
         session = Session.getInstance(props, new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
               PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
                     mailUser, mailPassword);
               return passwordAuthentication;
            }
         });
      } else {
         // SMTP does not require authentication
         session = Session.getInstance(props);
      }
   }
   /**
    * Indicates if the mail executor can send emails.
    *
    * @return true if the mail executor is ready to send emails
    */
   public boolean isReady() {
      return session != null;
   }
   /**
    * Create a message.
    *
    * @param toAddresses
    * @return a message
    */
   public Message createMessage(String... toAddresses) {
      return createMessage(Arrays.asList(toAddresses));
   }
   /**
    * Create a message.
    *
    * @param toAddresses
    * @return a message
    */
   public Message createMessage(List<String> toAddresses) {
      MimeMessage message = new MimeMessage(session);
      try {
         String fromAddress = settings.getString(Keys.mail.fromAddress, null);
         if (StringUtils.isEmpty(fromAddress)) {
            fromAddress = "gitblit@gitblit.com";
         }
         InternetAddress from = new InternetAddress(fromAddress, "Gitblit");
         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) {
            if (StringUtils.isEmpty(address)) {
               continue;
            }
            if (validEmail.matcher(address).find()) {
               try {
                  tos.add(new InternetAddress(address));
               } catch (Throwable t) {
               }
            }
         }
         message.setRecipients(Message.RecipientType.BCC,
               tos.toArray(new InternetAddress[tos.size()]));
         message.setSentDate(new Date());
      } catch (Exception e) {
         logger.error("Failed to properly create message", e);
      }
      return message;
   }
   /**
    * Returns the status of the mail queue.
    *
    * @return true, if the queue is empty
    */
   public boolean hasEmptyQueue() {
      return queue.isEmpty();
   }
   /**
    * Queue's an email message to be sent.
    *
    * @param message
    * @return true if the message was queued
    */
   public boolean queue(Message message) {
      if (!isReady()) {
         return false;
      }
      try {
         message.saveChanges();
      } catch (Throwable t) {
         logger.error("Failed to save changes to message!", t);
      }
      queue.add(message);
      return true;
   }
   @Override
   public void run() {
      if (!queue.isEmpty()) {
         if (session != null) {
            // send message via mail server
            List<Message> failures = new ArrayList<Message>();
            Message message = null;
            while ((message = queue.poll()) != null) {
               try {
                  if (settings.getBoolean(Keys.mail.debug, false)) {
                     logger.info("send: " + StringUtils.trimString(message.getSubject(), 60));
                  }
                  Transport.send(message);
               } catch (Throwable e) {
                  logger.error("Failed to send message", e);
                  failures.add(message);
               }
            }
            // push the failures back onto the queue for the next cycle
            queue.addAll(failures);
         }
      }
   }
   public void sendNow(Message message) throws Exception {
      Transport.send(message);
   }
}