James Moger
2012-02-03 fe7c01a8bd76dff240e74bb770212911e227ba59
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.Collections;
21 import java.util.Date;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Properties;
25 import java.util.Queue;
26 import java.util.Set;
27 import java.util.concurrent.ConcurrentLinkedQueue;
fa54be 28 import java.util.regex.Pattern;
831469 29
JM 30 import javax.mail.Authenticator;
31 import javax.mail.Message;
32 import javax.mail.Message.RecipientType;
33 import javax.mail.PasswordAuthentication;
34 import javax.mail.Session;
35 import javax.mail.Transport;
36 import javax.mail.internet.InternetAddress;
37 import javax.mail.internet.MimeMessage;
38
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.gitblit.utils.StringUtils;
43
44 /**
45  * The mail executor handles sending email messages asynchronously from queue.
46  * 
47  * @author James Moger
48  * 
49  */
50 public class MailExecutor implements Runnable {
51
52     private final Logger logger = LoggerFactory.getLogger(MailExecutor.class);
53
54     private final Queue<Message> queue = new ConcurrentLinkedQueue<Message>();
55
56     private final Set<Message> failures = Collections.synchronizedSet(new HashSet<Message>());
57
58     private final Session session;
59
60     private final IStoredSettings settings;
61
62     public MailExecutor(IStoredSettings settings) {
63         this.settings = settings;
64
65         final String mailUser = settings.getString(Keys.mail.username, null);
66         final String mailPassword = settings.getString(Keys.mail.password, null);
67         boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword);
68         String server = settings.getString(Keys.mail.server, "");
69         if (StringUtils.isEmpty(server)) {
70             session = null;
71             return;
72         }
73         int port = settings.getInteger(Keys.mail.port, 25);
74         boolean isGMail = false;
75         if (server.equals("smtp.gmail.com")) {
76             port = 465;
77             isGMail = true;
78         }
79
80         Properties props = new Properties();
81         props.setProperty("mail.smtp.host", server);
82         props.setProperty("mail.smtp.port", String.valueOf(port));
83         props.setProperty("mail.smtp.auth", String.valueOf(authenticate));
84         props.setProperty("mail.smtp.auths", String.valueOf(authenticate));
85
86         if (isGMail) {
87             props.setProperty("mail.smtp.starttls.enable", "true");
88             props.put("mail.smtp.socketFactory.port", String.valueOf(port));
89             props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
90             props.put("mail.smtp.socketFactory.fallback", "false");
91         }
92
93         if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
94             // SMTP requires authentication
95             session = Session.getInstance(props, new Authenticator() {
96                 protected PasswordAuthentication getPasswordAuthentication() {
97                     PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
98                             mailUser, mailPassword);
99                     return passwordAuthentication;
100                 }
101             });
102         } else {
103             // SMTP does not require authentication
104             session = Session.getInstance(props);
105         }
106     }
107
108     /**
109      * Indicates if the mail executor can send emails.
110      * 
111      * @return true if the mail executor is ready to send emails
112      */
113     public boolean isReady() {
114         return session != null;
115     }
116
117     /**
118      * Creates a message for the administrators.
119      * 
120      * @returna message
121      */
122     public Message createMessageForAdministrators() {
123         List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
124         if (toAddresses.size() == 0) {
125             logger.warn("Can not notify administrators because no email addresses are defined!");
126             return null;
127         }
128         return createMessage(toAddresses);
129     }
130
131     /**
132      * Create a message.
133      * 
134      * @param toAddresses
135      * @return a message
136      */
137     public Message createMessage(String... toAddresses) {
138         return createMessage(Arrays.asList(toAddresses));
139     }
140
141     /**
142      * Create a message.
143      * 
144      * @param toAddresses
145      * @return a message
146      */
147     public Message createMessage(List<String> toAddresses) {
148         MimeMessage message = new MimeMessage(session);
149         try {
7e099b 150             String fromAddress = settings.getString(Keys.mail.fromAddress, null);
JM 151             if (StringUtils.isEmpty(fromAddress)) {
152                 fromAddress = "gitblit@gitblit.com";
153             }
154             InternetAddress from = new InternetAddress(fromAddress, "Gitblit");
831469 155             message.setFrom(from);
JM 156
3a2c57 157             // determine unique set of addresses
JM 158             Set<String> uniques = new HashSet<String>();
159             for (String address : toAddresses) {
160                 uniques.add(address.toLowerCase());
161             }
162             
fa54be 163             Pattern validEmail = Pattern
JM 164                     .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})(\\]?)$");
165             List<InternetAddress> tos = new ArrayList<InternetAddress>();
166             for (String address : uniques) {
167                 if (StringUtils.isEmpty(address)) {
168                     continue;
169                 }
170                 if (validEmail.matcher(address).find()) {
171                     try {
172                         tos.add(new InternetAddress(address));
173                     } catch (Throwable t) {
174                     }
175                 }
3a2c57 176             }            
JM 177             message.setRecipients(Message.RecipientType.BCC,
fa54be 178                     tos.toArray(new InternetAddress[tos.size()]));
831469 179             message.setSentDate(new Date());
JM 180         } catch (Exception e) {
181             logger.error("Failed to properly create message", e);
182         }
183         return message;
184     }
185
186     /**
7e099b 187      * Returns the status of the mail queue.
JM 188      * 
189      * @return true, if the queue is empty
190      */
191     public boolean hasEmptyQueue() {
192         return queue.isEmpty();
193     }
194
195     /**
831469 196      * Queue's an email message to be sent.
JM 197      * 
198      * @param message
199      * @return true if the message was queued
200      */
201     public boolean queue(Message message) {
202         if (!isReady()) {
203             return false;
204         }
205         try {
206             message.saveChanges();
207         } catch (Throwable t) {
208             logger.error("Failed to save changes to message!", t);
209         }
210         queue.add(message);
211         return true;
212     }
213
214     @Override
215     public void run() {
216         if (!queue.isEmpty()) {
217             if (session != null) {
218                 // send message via mail server
219                 Message message = null;
220                 while ((message = queue.peek()) != null) {
221                     try {
222                         if (settings.getBoolean(Keys.mail.debug, false)) {
223                             logger.info("send: "
224                                     + StringUtils.trimString(
225                                             message.getSubject()
226                                                     + " => "
227                                                     + message.getRecipients(RecipientType.TO)[0]
228                                                             .toString(), 60));
229                         }
230                         Transport.send(message);
231                         queue.remove();
232                         failures.remove(message);
233                     } catch (Throwable e) {
234                         if (!failures.contains(message)) {
235                             logger.error("Failed to send message", e);
236                             failures.add(message);
237                         }
238                     }
239                 }
240             }
241         } else {
242             // log message to console and drop
243             if (!queue.isEmpty()) {
244                 Message message = null;
245                 while ((message = queue.peek()) != null) {
246                     try {
247                         logger.info("drop: "
248                                 + StringUtils.trimString(
249                                         (message.getSubject())
250                                                 + " => "
251                                                 + message.getRecipients(RecipientType.TO)[0]
252                                                         .toString(), 60));
253                         queue.remove();
254                         failures.remove(message);
255                     } catch (Throwable e) {
256                         if (!failures.contains(message)) {
257                             logger.error("Failed to remove message from queue");
258                             failures.add(message);
259                         }
260                     }
261                 }
262             }
263         }
264     }
265 }