James Moger
2012-09-10 fabe060d3a435f116128851f828e35c2af5fde67
commit | author | age
831469 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit;
17
fa54be 18 import java.util.ArrayList;
831469 19 import java.util.Arrays;
JM 20 import java.util.Date;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Properties;
24 import java.util.Queue;
25 import java.util.Set;
26 import java.util.concurrent.ConcurrentLinkedQueue;
fa54be 27 import java.util.regex.Pattern;
831469 28
JM 29 import javax.mail.Authenticator;
30 import javax.mail.Message;
31 import javax.mail.PasswordAuthentication;
32 import javax.mail.Session;
33 import javax.mail.Transport;
34 import javax.mail.internet.InternetAddress;
35 import javax.mail.internet.MimeMessage;
36
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.gitblit.utils.StringUtils;
41
42 /**
43  * The mail executor handles sending email messages asynchronously from queue.
44  * 
45  * @author James Moger
46  * 
47  */
48 public class MailExecutor implements Runnable {
49
50     private final Logger logger = LoggerFactory.getLogger(MailExecutor.class);
51
52     private final Queue<Message> queue = new ConcurrentLinkedQueue<Message>();
53
54     private final Session session;
55
56     private final IStoredSettings settings;
57
58     public MailExecutor(IStoredSettings settings) {
59         this.settings = settings;
60
61         final String mailUser = settings.getString(Keys.mail.username, null);
62         final String mailPassword = settings.getString(Keys.mail.password, null);
63         boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword);
64         String server = settings.getString(Keys.mail.server, "");
65         if (StringUtils.isEmpty(server)) {
66             session = null;
67             return;
68         }
69         int port = settings.getInteger(Keys.mail.port, 25);
70         boolean isGMail = false;
71         if (server.equals("smtp.gmail.com")) {
72             port = 465;
73             isGMail = true;
74         }
75
76         Properties props = new Properties();
77         props.setProperty("mail.smtp.host", server);
78         props.setProperty("mail.smtp.port", String.valueOf(port));
79         props.setProperty("mail.smtp.auth", String.valueOf(authenticate));
80         props.setProperty("mail.smtp.auths", String.valueOf(authenticate));
81
82         if (isGMail) {
83             props.setProperty("mail.smtp.starttls.enable", "true");
84             props.put("mail.smtp.socketFactory.port", String.valueOf(port));
85             props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
86             props.put("mail.smtp.socketFactory.fallback", "false");
87         }
88
89         if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
90             // SMTP requires authentication
91             session = Session.getInstance(props, new Authenticator() {
92                 protected PasswordAuthentication getPasswordAuthentication() {
93                     PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
94                             mailUser, mailPassword);
95                     return passwordAuthentication;
96                 }
97             });
98         } else {
99             // SMTP does not require authentication
100             session = Session.getInstance(props);
101         }
102     }
103
104     /**
105      * Indicates if the mail executor can send emails.
106      * 
107      * @return true if the mail executor is ready to send emails
108      */
109     public boolean isReady() {
110         return session != null;
111     }
112
113     /**
114      * Creates a message for the administrators.
115      * 
116      * @returna message
117      */
118     public Message createMessageForAdministrators() {
119         List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
120         if (toAddresses.size() == 0) {
121             logger.warn("Can not notify administrators because no email addresses are defined!");
122             return null;
123         }
124         return createMessage(toAddresses);
125     }
126
127     /**
128      * Create a message.
129      * 
130      * @param toAddresses
131      * @return a message
132      */
133     public Message createMessage(String... toAddresses) {
134         return createMessage(Arrays.asList(toAddresses));
135     }
136
137     /**
138      * Create a message.
139      * 
140      * @param toAddresses
141      * @return a message
142      */
143     public Message createMessage(List<String> toAddresses) {
144         MimeMessage message = new MimeMessage(session);
145         try {
7e099b 146             String fromAddress = settings.getString(Keys.mail.fromAddress, null);
JM 147             if (StringUtils.isEmpty(fromAddress)) {
148                 fromAddress = "gitblit@gitblit.com";
149             }
150             InternetAddress from = new InternetAddress(fromAddress, "Gitblit");
831469 151             message.setFrom(from);
JM 152
3a2c57 153             // determine unique set of addresses
JM 154             Set<String> uniques = new HashSet<String>();
155             for (String address : toAddresses) {
156                 uniques.add(address.toLowerCase());
157             }
158             
fa54be 159             Pattern validEmail = Pattern
JM 160                     .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})(\\]?)$");
161             List<InternetAddress> tos = new ArrayList<InternetAddress>();
162             for (String address : uniques) {
163                 if (StringUtils.isEmpty(address)) {
164                     continue;
165                 }
166                 if (validEmail.matcher(address).find()) {
167                     try {
168                         tos.add(new InternetAddress(address));
169                     } catch (Throwable t) {
170                     }
171                 }
3a2c57 172             }            
JM 173             message.setRecipients(Message.RecipientType.BCC,
fa54be 174                     tos.toArray(new InternetAddress[tos.size()]));
831469 175             message.setSentDate(new Date());
JM 176         } catch (Exception e) {
177             logger.error("Failed to properly create message", e);
178         }
179         return message;
180     }
181
182     /**
7e099b 183      * Returns the status of the mail queue.
JM 184      * 
185      * @return true, if the queue is empty
186      */
187     public boolean hasEmptyQueue() {
188         return queue.isEmpty();
189     }
190
191     /**
831469 192      * Queue's an email message to be sent.
JM 193      * 
194      * @param message
195      * @return true if the message was queued
196      */
197     public boolean queue(Message message) {
198         if (!isReady()) {
199             return false;
200         }
201         try {
202             message.saveChanges();
203         } catch (Throwable t) {
204             logger.error("Failed to save changes to message!", t);
205         }
206         queue.add(message);
207         return true;
208     }
209
210     @Override
211     public void run() {
212         if (!queue.isEmpty()) {
213             if (session != null) {
214                 // send message via mail server
c6b6bd 215                 List<Message> failures = new ArrayList<Message>();
831469 216                 Message message = null;
c6b6bd 217                 while ((message = queue.poll()) != null) {
831469 218                     try {
JM 219                         if (settings.getBoolean(Keys.mail.debug, false)) {
c6b6bd 220                             logger.info("send: " + StringUtils.trimString(message.getSubject(), 60));
831469 221                         }
JM 222                         Transport.send(message);
223                     } catch (Throwable e) {
c6b6bd 224                         logger.error("Failed to send message", e);
JM 225                         failures.add(message);
831469 226                     }
JM 227                 }
c6b6bd 228                 
JM 229                 // push the failures back onto the queue for the next cycle
230                 queue.addAll(failures);
831469 231             }
JM 232         }
233     }
234 }