James Moger
2011-09-25 dd9ae71bc1cb13b90dcc8d9689550eb7dfe7d035
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
18 import java.util.Arrays;
19 import java.util.Collections;
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;
27
28 import javax.mail.Authenticator;
29 import javax.mail.Message;
30 import javax.mail.Message.RecipientType;
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 Set<Message> failures = Collections.synchronizedSet(new HashSet<Message>());
55
56     private final Session session;
57
58     private final IStoredSettings settings;
59
60     public MailExecutor(IStoredSettings settings) {
61         this.settings = settings;
62
63         final String mailUser = settings.getString(Keys.mail.username, null);
64         final String mailPassword = settings.getString(Keys.mail.password, null);
65         boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword);
66         String server = settings.getString(Keys.mail.server, "");
67         if (StringUtils.isEmpty(server)) {
68             session = null;
69             return;
70         }
71         int port = settings.getInteger(Keys.mail.port, 25);
72         boolean isGMail = false;
73         if (server.equals("smtp.gmail.com")) {
74             port = 465;
75             isGMail = true;
76         }
77
78         Properties props = new Properties();
79         props.setProperty("mail.smtp.host", server);
80         props.setProperty("mail.smtp.port", String.valueOf(port));
81         props.setProperty("mail.smtp.auth", String.valueOf(authenticate));
82         props.setProperty("mail.smtp.auths", String.valueOf(authenticate));
83
84         if (isGMail) {
85             props.setProperty("mail.smtp.starttls.enable", "true");
86             props.put("mail.smtp.socketFactory.port", String.valueOf(port));
87             props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
88             props.put("mail.smtp.socketFactory.fallback", "false");
89         }
90
91         if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
92             // SMTP requires authentication
93             session = Session.getInstance(props, new Authenticator() {
94                 protected PasswordAuthentication getPasswordAuthentication() {
95                     PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
96                             mailUser, mailPassword);
97                     return passwordAuthentication;
98                 }
99             });
100         } else {
101             // SMTP does not require authentication
102             session = Session.getInstance(props);
103         }
104     }
105
106     /**
107      * Indicates if the mail executor can send emails.
108      * 
109      * @return true if the mail executor is ready to send emails
110      */
111     public boolean isReady() {
112         return session != null;
113     }
114
115     /**
116      * Creates a message for the administrators.
117      * 
118      * @returna message
119      */
120     public Message createMessageForAdministrators() {
121         List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
122         if (toAddresses.size() == 0) {
123             logger.warn("Can not notify administrators because no email addresses are defined!");
124             return null;
125         }
126         return createMessage(toAddresses);
127     }
128
129     /**
130      * Create a message.
131      * 
132      * @param toAddresses
133      * @return a message
134      */
135     public Message createMessage(String... toAddresses) {
136         return createMessage(Arrays.asList(toAddresses));
137     }
138
139     /**
140      * Create a message.
141      * 
142      * @param toAddresses
143      * @return a message
144      */
145     public Message createMessage(List<String> toAddresses) {
146         MimeMessage message = new MimeMessage(session);
147         try {
148             InternetAddress from = new InternetAddress(settings.getString(Keys.mail.fromAddress,
149                     "gitblit@gitblit.com"), "Gitblit");
150             message.setFrom(from);
151
152             InternetAddress[] tos = new InternetAddress[toAddresses.size()];
153             for (int i = 0; i < toAddresses.size(); i++) {
154                 tos[i] = new InternetAddress(toAddresses.get(i));
155             }
156             message.setRecipients(Message.RecipientType.TO, tos);
157             message.setSentDate(new Date());
158         } catch (Exception e) {
159             logger.error("Failed to properly create message", e);
160         }
161         return message;
162     }
163
164     /**
165      * Queue's an email message to be sent.
166      * 
167      * @param message
168      * @return true if the message was queued
169      */
170     public boolean queue(Message message) {
171         if (!isReady()) {
172             return false;
173         }
174         try {
175             message.saveChanges();
176         } catch (Throwable t) {
177             logger.error("Failed to save changes to message!", t);
178         }
179         queue.add(message);
180         return true;
181     }
182
183     @Override
184     public void run() {
185         if (!queue.isEmpty()) {
186             if (session != null) {
187                 // send message via mail server
188                 Message message = null;
189                 while ((message = queue.peek()) != null) {
190                     try {
191                         if (settings.getBoolean(Keys.mail.debug, false)) {
192                             logger.info("send: "
193                                     + StringUtils.trimString(
194                                             message.getSubject()
195                                                     + " => "
196                                                     + message.getRecipients(RecipientType.TO)[0]
197                                                             .toString(), 60));
198                         }
199                         Transport.send(message);
200                         queue.remove();
201                         failures.remove(message);
202                     } catch (Throwable e) {
203                         if (!failures.contains(message)) {
204                             logger.error("Failed to send message", e);
205                             failures.add(message);
206                         }
207                     }
208                 }
209             }
210         } else {
211             // log message to console and drop
212             if (!queue.isEmpty()) {
213                 Message message = null;
214                 while ((message = queue.peek()) != null) {
215                     try {
216                         logger.info("drop: "
217                                 + StringUtils.trimString(
218                                         (message.getSubject())
219                                                 + " => "
220                                                 + message.getRecipients(RecipientType.TO)[0]
221                                                         .toString(), 60));
222                         queue.remove();
223                         failures.remove(message);
224                     } catch (Throwable e) {
225                         if (!failures.contains(message)) {
226                             logger.error("Failed to remove message from queue");
227                             failures.add(message);
228                         }
229                     }
230                 }
231             }
232         }
233     }
234 }