]> source.dussan.org Git - sonarqube.git/blob
382e9ce4f95ddfb420691984664cb0299fb642a1
[sonarqube.git] /
1 /*
2  * Sonar, open source software quality management tool.
3  * Copyright (C) 2008-2012 SonarSource
4  * mailto:contact AT sonarsource DOT com
5  *
6  * Sonar is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * Sonar is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with Sonar; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
19  */
20 package org.sonar.plugins.emailnotifications;
21
22 import org.apache.commons.lang.StringUtils;
23 import org.apache.commons.mail.EmailException;
24 import org.apache.commons.mail.SimpleEmail;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27 import org.sonar.api.database.model.User;
28 import org.sonar.api.notifications.Notification;
29 import org.sonar.api.notifications.NotificationChannel;
30 import org.sonar.api.platform.EmailSettings;
31 import org.sonar.api.security.UserFinder;
32 import org.sonar.api.utils.SonarException;
33 import org.sonar.plugins.emailnotifications.api.EmailMessage;
34 import org.sonar.plugins.emailnotifications.api.EmailTemplate;
35
36 import java.net.MalformedURLException;
37 import java.net.URL;
38
39 /**
40  * References:
41  * <ul>
42  * <li><a href="http://tools.ietf.org/html/rfc4021">Registration of Mail and MIME Header Fields</a></li>
43  * <li><a href="http://tools.ietf.org/html/rfc2919">List-Id: A Structured Field and Namespace for the Identification of Mailing Lists</a></li>
44  * <li><a href="https://github.com/blog/798-threaded-email-notifications">GitHub: Threaded Email Notifications</a></li>
45  * </ul>
46  *
47  * @since 2.10
48  */
49 public class EmailNotificationChannel extends NotificationChannel {
50
51   private static final Logger LOG = LoggerFactory.getLogger(EmailNotificationChannel.class);
52
53   /**
54    * @see org.apache.commons.mail.Email#setSocketConnectionTimeout(int)
55    * @see org.apache.commons.mail.Email#setSocketTimeout(int)
56    */
57   private static final int SOCKET_TIMEOUT = 30000;
58
59   /**
60    * Email Header Field: "List-ID".
61    * Value of this field should contain mailing list identifier as specified in <a href="http://tools.ietf.org/html/rfc2919">RFC 2919</a>.
62    */
63   private static final String LIST_ID_HEADER = "List-ID";
64
65   /**
66    * Email Header Field: "List-Archive".
67    * Value of this field should contain URL of mailing list archive as specified in <a href="http://tools.ietf.org/html/rfc2369">RFC 2369</a>.
68    */
69   private static final String LIST_ARCHIVE_HEADER = "List-Archive";
70
71   /**
72    * Email Header Field: "In-Reply-To".
73    * Value of this field should contain related message identifier as specified in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a>.
74    */
75   private static final String IN_REPLY_TO_HEADER = "In-Reply-To";
76
77   /**
78    * Email Header Field: "References".
79    * Value of this field should contain related message identifier as specified in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a>
80    */
81   private static final String REFERENCES_HEADER = "References";
82
83   private static final String FROM_NAME_DEFAULT = "Sonar";
84   private static final String SUBJECT_DEFAULT = "Notification";
85
86   private EmailSettings configuration;
87   private EmailTemplate[] templates;
88   private UserFinder userFinder;
89
90   public EmailNotificationChannel(EmailSettings configuration, EmailTemplate[] templates, UserFinder userFinder) {
91     this.configuration = configuration;
92     this.templates = templates;
93     this.userFinder = userFinder;
94   }
95
96   @Override
97   public void deliver(Notification notification, String username) {
98     User user = userFinder.findByLogin(username);
99     if (StringUtils.isBlank(user.getEmail())) {
100       LOG.debug("Email not defined for user: " + username);
101       return;
102     }
103     EmailMessage emailMessage = format(notification);
104     if (emailMessage != null) {
105       emailMessage.setTo(user.getEmail());
106       deliver(emailMessage);
107     }
108   }
109
110   private EmailMessage format(Notification notification) {
111     for (EmailTemplate template : templates) {
112       EmailMessage email = template.format(notification);
113       if (email != null) {
114         return email;
115       }
116     }
117     LOG.warn("Email template not found for notification: {}", notification);
118     return null;
119   }
120
121   /**
122    * Visibility has been relaxed for tests.
123    */
124   void deliver(EmailMessage emailMessage) {
125     if (StringUtils.isBlank(configuration.getSmtpHost())) {
126       LOG.debug("SMTP host was not configured - email will not be sent");
127       return;
128     }
129     try {
130       send(emailMessage);
131     } catch (EmailException e) {
132       LOG.error("Unable to send email", e);
133     }
134   }
135
136   private void send(EmailMessage emailMessage) throws EmailException {
137     // Trick to correctly initilize javax.mail library
138     ClassLoader classloader = Thread.currentThread().getContextClassLoader();
139     Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
140
141     try {
142       LOG.debug("Sending email: {}", emailMessage);
143       String host = null;
144       try {
145         host = new URL(configuration.getServerBaseURL()).getHost();
146       } catch (MalformedURLException e) {
147         // ignore
148       }
149
150       SimpleEmail email = new SimpleEmail();
151       if (StringUtils.isNotBlank(host)) {
152         /*
153         * Set headers for proper threading: GMail will not group messages, even if they have same subject, but don't have "In-Reply-To" and
154         * "References" headers. TODO investigate threading in other clients like KMail, Thunderbird, Outlook
155         */
156         if (StringUtils.isNotEmpty(emailMessage.getMessageId())) {
157           String messageId = "<" + emailMessage.getMessageId() + "@" + host + ">";
158           email.addHeader(IN_REPLY_TO_HEADER, messageId);
159           email.addHeader(REFERENCES_HEADER, messageId);
160         }
161         // Set headers for proper filtering
162         email.addHeader(LIST_ID_HEADER, "Sonar <sonar." + host + ">");
163         email.addHeader(LIST_ARCHIVE_HEADER, configuration.getServerBaseURL());
164       }
165       // Set general information
166       email.setCharset("UTF-8");
167       String from = StringUtils.isBlank(emailMessage.getFrom()) ? FROM_NAME_DEFAULT : emailMessage.getFrom() + " (Sonar)";
168       email.setFrom(configuration.getFrom(), from);
169       email.addTo(emailMessage.getTo(), " ");
170       String subject = StringUtils.defaultIfBlank(StringUtils.trimToEmpty(configuration.getPrefix()) + " ", "")
171           + StringUtils.defaultString(emailMessage.getSubject(), SUBJECT_DEFAULT);
172       email.setSubject(subject);
173       email.setMsg(emailMessage.getMessage());
174       // Send
175       email.setHostName(configuration.getSmtpHost());
176       if (StringUtils.equalsIgnoreCase(configuration.getSecureConnection(), "SSL")) {
177         email.setSSL(true);
178         email.setSslSmtpPort(configuration.getSmtpPort());
179
180         // this port is not used except in EmailException message, that's why it's set with the same value than SSL port.
181         // It prevents from getting bad message.
182         email.setSmtpPort(Integer.parseInt(configuration.getSmtpPort()));
183       } else if (StringUtils.isBlank(configuration.getSecureConnection())) {
184         email.setSmtpPort(Integer.parseInt(configuration.getSmtpPort()));
185       } else {
186         throw new SonarException("Unknown type of SMTP secure connection: " + configuration.getSecureConnection());
187       }
188       if (StringUtils.isNotBlank(configuration.getSmtpUsername()) || StringUtils.isNotBlank(configuration.getSmtpPassword())) {
189         email.setAuthentication(configuration.getSmtpUsername(), configuration.getSmtpPassword());
190       }
191       email.setSocketConnectionTimeout(SOCKET_TIMEOUT);
192       email.setSocketTimeout(SOCKET_TIMEOUT);
193       email.send();
194
195     } finally {
196       Thread.currentThread().setContextClassLoader(classloader);
197     }
198   }
199
200   /**
201    * Send test email. This method called from Ruby.
202    *
203    * @throws EmailException when unable to send
204    */
205   public void sendTestEmail(String toAddress, String subject, String message) throws EmailException {
206     try {
207       EmailMessage emailMessage = new EmailMessage();
208       emailMessage.setTo(toAddress);
209       emailMessage.setSubject(subject);
210       emailMessage.setMessage(message);
211       send(emailMessage);
212     } catch (EmailException e) {
213       LOG.error("Fail to send test email to: " + toAddress, e);
214       throw e;
215     }
216   }
217
218 }