James Moger
2011-10-31 17820f3a1153250a325fed23dfc2da59ce6ba777
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 {
7e099b 148             String fromAddress = settings.getString(Keys.mail.fromAddress, null);
JM 149             if (StringUtils.isEmpty(fromAddress)) {
150                 fromAddress = "gitblit@gitblit.com";
151             }
152             InternetAddress from = new InternetAddress(fromAddress, "Gitblit");
831469 153             message.setFrom(from);
JM 154
155             InternetAddress[] tos = new InternetAddress[toAddresses.size()];
156             for (int i = 0; i < toAddresses.size(); i++) {
157                 tos[i] = new InternetAddress(toAddresses.get(i));
158             }
159             message.setRecipients(Message.RecipientType.TO, tos);
160             message.setSentDate(new Date());
161         } catch (Exception e) {
162             logger.error("Failed to properly create message", e);
163         }
164         return message;
165     }
166
167     /**
7e099b 168      * Returns the status of the mail queue.
JM 169      * 
170      * @return true, if the queue is empty
171      */
172     public boolean hasEmptyQueue() {
173         return queue.isEmpty();
174     }
175
176     /**
831469 177      * Queue's an email message to be sent.
JM 178      * 
179      * @param message
180      * @return true if the message was queued
181      */
182     public boolean queue(Message message) {
183         if (!isReady()) {
184             return false;
185         }
186         try {
187             message.saveChanges();
188         } catch (Throwable t) {
189             logger.error("Failed to save changes to message!", t);
190         }
191         queue.add(message);
192         return true;
193     }
194
195     @Override
196     public void run() {
197         if (!queue.isEmpty()) {
198             if (session != null) {
199                 // send message via mail server
200                 Message message = null;
201                 while ((message = queue.peek()) != null) {
202                     try {
203                         if (settings.getBoolean(Keys.mail.debug, false)) {
204                             logger.info("send: "
205                                     + StringUtils.trimString(
206                                             message.getSubject()
207                                                     + " => "
208                                                     + message.getRecipients(RecipientType.TO)[0]
209                                                             .toString(), 60));
210                         }
211                         Transport.send(message);
212                         queue.remove();
213                         failures.remove(message);
214                     } catch (Throwable e) {
215                         if (!failures.contains(message)) {
216                             logger.error("Failed to send message", e);
217                             failures.add(message);
218                         }
219                     }
220                 }
221             }
222         } else {
223             // log message to console and drop
224             if (!queue.isEmpty()) {
225                 Message message = null;
226                 while ((message = queue.peek()) != null) {
227                     try {
228                         logger.info("drop: "
229                                 + StringUtils.trimString(
230                                         (message.getSubject())
231                                                 + " => "
232                                                 + message.getRecipients(RecipientType.TO)[0]
233                                                         .toString(), 60));
234                         queue.remove();
235                         failures.remove(message);
236                     } catch (Throwable e) {
237                         if (!failures.contains(message)) {
238                             logger.error("Failed to remove message from queue");
239                             failures.add(message);
240                         }
241                     }
242                 }
243             }
244         }
245     }
246 }