View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.kernel.mail;
23  
24  import org.apache.commons.lang3.BooleanUtils;
25  import org.apache.commons.lang3.StringUtils;
26  import org.osgi.service.cm.ConfigurationException;
27  import org.osgi.service.cm.ManagedService;
28  import org.osgi.service.component.annotations.Component;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import java.util.Dictionary;
33  
34  import javax.mail.Message.RecipientType;
35  import javax.mail.MessagingException;
36  import javax.mail.internet.InternetAddress;
37  import javax.mail.internet.MimeBodyPart;
38  import javax.mail.internet.MimeMessage;
39  import javax.mail.internet.MimeMultipart;
40  
41  /**
42   * OSGi service that allows to send e-mails using <code>javax.mail</code>.
43   */
44  @Component(
45      immediate = true,
46      service = { ManagedService.class,SmtpService.class },
47      property = {
48          "service.description=SMTP Service"
49      }
50  )
51  public class SmtpService extends BaseSmtpService implements ManagedService {
52  
53    /** The logging facility */
54    private static final Logger logger = LoggerFactory.getLogger(SmtpService.class);
55  
56    /** Parameter name for the test setting */
57    private static final String OPT_MAIL_TEST = "mail.test";
58  
59    /** Parameter name for the mode setting */
60    private static final String OPT_MAIL_MODE = "mail.mode";
61  
62    /**
63     * Pattern to split strings containing lists of emails. This pattern matches any number of contiguous spaces or commas
64     */
65    private static final String SPLIT_PATTERN = "[\\s,]+";
66  
67    /** Define the MIME type for text mail content */
68    private static final String TEXT_PLAIN = "text/plain; charset=UTF-8";
69  
70    /** Define the MIME type for HTML mail content */
71    private static final String TEXT_HTML = "text/html; charset=UTF-8";
72  
73    /**
74     * Callback from the OSGi <code>ConfigurationAdmin</code> on configuration changes.
75     *
76     * @param properties
77     *          the configuration properties
78     * @throws ConfigurationException
79     *           if configuration fails
80     */
81    @Override
82    public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
83  
84      // Production or test mode
85      String optMode = StringUtils.trimToNull((String) properties.get(OPT_MAIL_MODE));
86      if (optMode != null) {
87        try {
88          Mode mode = Mode.valueOf(optMode);
89          setProductionMode(Mode.production.equals(mode));
90        } catch (Exception e) {
91          logger.error("Error parsing smtp service mode '{}': {}", optMode, e.getMessage());
92          throw new ConfigurationException(OPT_MAIL_MODE, e.getMessage());
93        }
94      }
95      logger.info("Smtp service is in {} mode", isProductionMode() ? "production" : "test");
96  
97      // Mail transport protocol
98      String optMailTransport = StringUtils.trimToNull((String) properties.get(OPT_MAIL_TRANSPORT));
99      if (StringUtils.isNotBlank(optMailTransport))
100       setMailTransport(optMailTransport);
101 
102     // The mail host is mandatory
103     String propName = OPT_MAIL_PREFIX + mailTransport + OPT_MAIL_HOST_SUFFIX;
104     String mailHost = (String) properties.get(propName);
105     if (StringUtils.isBlank(mailHost))
106       throw new ConfigurationException(propName, "is not set");
107     setHost(mailHost);
108 
109     // Mail port
110     propName = OPT_MAIL_PREFIX + mailTransport + OPT_MAIL_PORT_SUFFIX;
111     String mailPort = (String) properties.get(propName);
112     if (StringUtils.isNotBlank(mailPort))
113       setPort(Integer.parseInt(mailPort));
114 
115     // TSL over SMTP support
116     propName = OPT_MAIL_PREFIX + mailTransport + OPT_MAIL_TLS_ENABLE_SUFFIX;
117     setSsl(BooleanUtils.toBoolean((String) properties.get(propName)));
118 
119     // Mail user
120     String mailUser = (String) properties.get(OPT_MAIL_USER);
121     if (StringUtils.isNotBlank(mailUser))
122       setUser(mailUser);
123 
124     // Mail password
125     String mailPassword = (String) properties.get(OPT_MAIL_PASSWORD);
126     if (StringUtils.isNotBlank(mailPassword))
127       setPassword(mailPassword);
128 
129     // Mail sender
130     String mailFrom = (String) properties.get(OPT_MAIL_FROM);
131     if (StringUtils.isNotBlank(mailFrom))
132       setSender(mailFrom);
133 
134     // Mail debugging
135     setDebug(BooleanUtils.toBoolean((String) properties.get(OPT_MAIL_DEBUG)));
136 
137     configure();
138 
139     // Test
140     String mailTest = StringUtils.trimToNull((String) properties.get(OPT_MAIL_TEST));
141     if (mailTest != null && Boolean.parseBoolean(mailTest)) {
142       logger.info("Sending test message to {}", mailFrom);
143       try {
144         sendTestMessage(mailFrom);
145       } catch (MessagingException e) {
146         logger.error("Error sending test message to " + mailFrom + ": " + e.getMessage());
147         while (e.getNextException() != null) {
148           Exception ne = e.getNextException();
149           logger.error("Error sending test message to " + mailFrom + ": " + ne.getMessage());
150           if (ne instanceof MessagingException)
151             e = (MessagingException) ne;
152           else
153             break;
154         }
155         throw new ConfigurationException(OPT_MAIL_PREFIX + mailTransport + OPT_MAIL_HOST_SUFFIX,
156                 "Failed to send test message to " + mailFrom);
157       }
158     }
159 
160   }
161 
162   /**
163    * Method to send a test message.
164    *
165    * @throws MessagingException
166    *           if sending the message failed
167    */
168   private void sendTestMessage(String recipient) throws MessagingException {
169     send(recipient, "Test from Opencast", "Hello world");
170   }
171 
172   /**
173    * Method to send a message
174    *
175    * @param to
176    *          Recipient of the message
177    * @param subject
178    *          Subject of the message
179    * @param bodyText
180    *          Text body of the message (null if there should be no text part)
181    * @throws MessagingException
182    *           if sending the message failed
183    */
184   public void send(String to, String subject, String bodyText) throws MessagingException {
185     send(to, subject, bodyText, null);
186   }
187 
188   /**
189    * Method to send a message
190    *
191    * @param to
192    *          Recipient of the message
193    * @param subject
194    *          Subject of the message
195    * @param bodyText
196    *          Text body of the message (null if there should be no text part)
197    * @param bodyHTML
198    *          HTML body of the message (null if there should be no HTML part)
199    * @throws MessagingException
200    *           if sending the message failed
201    */
202   public void send(String to, String subject, String bodyText, String bodyHTML) throws MessagingException {
203     send(to, null, null, subject, bodyText, bodyHTML);
204   }
205 
206   /**
207    * Send a message to multiple recipients
208    *
209    * @param to
210    *          "To:" message recipient(s), separated by commas and/or spaces
211    * @param cc
212    *          "CC:" message recipient(s), separated by commas and/or spaces
213    * @param bcc
214    *          "BCC:" message recipient(s), separated by commas and/or spaces
215    * @param subject
216    *          Subject of the message
217    * @param bodyText
218    *          Text body of the message (null if there should be no text part)
219    * @throws MessagingException
220    *           if sending the message failed
221    */
222   public void send(String to, String cc, String bcc, String subject, String bodyText) throws MessagingException {
223     send(to, cc, bcc, subject, bodyText, null);
224   }
225 
226   /**
227    * Send a message to multiple recipients
228    *
229    * @param to
230    *          "To:" message recipient(s), separated by commas and/or spaces
231    * @param cc
232    *          "CC:" message recipient(s), separated by commas and/or spaces
233    * @param bcc
234    *          "BCC:" message recipient(s), separated by commas and/or spaces
235    * @param subject
236    *          Subject of the message
237    * @param bodyText
238    *          Text body of the message (null if there should be no text part)
239    * @param bodyHTML
240    *          HTML body of the message (null if there should be no HTML part)
241    * @throws MessagingException
242    *           if sending the message failed
243    */
244   public void send(String to, String cc, String bcc, String subject, String bodyText, String bodyHTML) throws MessagingException {
245     String[] toArray = null;
246     String[] ccArray = null;
247     String[] bccArray = null;
248 
249     if (to != null)
250       toArray = to.trim().split(SPLIT_PATTERN, 0);
251 
252     if (cc != null)
253       ccArray = cc.trim().split(SPLIT_PATTERN, 0);
254 
255     if (bcc != null)
256       bccArray = bcc.trim().split(SPLIT_PATTERN, 0);
257 
258     send(toArray, ccArray, bccArray, subject, bodyText, bodyHTML);
259   }
260 
261   /**
262    * Send a message to multiple recipients
263    *
264    * @param to
265    *          Array with the "To:" recipients of the message
266    * @param cc
267    *          Array with the "CC:" recipients of the message
268    * @param bcc
269    *          Array with the "BCC:" recipients of the message
270    * @param subject
271    *          Subject of the message
272    * @param bodyText
273    *          Text body of the message (null if there should be no text part)
274    * @throws MessagingException
275    *           if sending the message failed
276    */
277   public void send(String[] to, String[] cc, String[] bcc, String subject, String bodyText) throws MessagingException {
278     send(to, cc, bcc, subject, bodyText, null);
279   }
280 
281   /**
282    * Send a message to multiple recipients
283    *
284    * @param to
285    *          Array with the "To:" recipients of the message
286    * @param cc
287    *          Array with the "CC:" recipients of the message
288    * @param bcc
289    *          Array with the "BCC:" recipients of the message
290    * @param subject
291    *          Subject of the message
292    * @param bodyText
293    *          Text body of the message (null if there should be no text part)
294    * @param bodyHTML
295    *          HTML body of the message (null if there should be no HTML part)
296    * @throws MessagingException
297    *          if sending the message failed
298    * @throws IllegalArgumentException
299    *          if both bodyText and bodyHTML are null
300    */
301   public void send(String[] to, String[] cc, String[] bcc, String subject, String bodyText, String bodyHTML) throws MessagingException {
302     if (bodyText == null && bodyHTML == null) {
303       throw new IllegalArgumentException("bodyText and bodyHTML cannot both be empty");
304     }
305 
306     MimeMessage message = createMessage();
307     if (StringUtils.isNotBlank(getSender())) {
308       message.addFrom(new InternetAddress[] { new InternetAddress(getSender()) });
309     }
310     addRecipients(message, RecipientType.TO, to);
311     addRecipients(message, RecipientType.CC, cc);
312     addRecipients(message, RecipientType.BCC, bcc);
313     message.setSubject(subject);
314 
315     MimeMultipart mp = new MimeMultipart("alternative");
316     // the text/plain part needs to be added first!
317     if (bodyText != null) {
318       MimeBodyPart part = new MimeBodyPart();
319       part.setContent(bodyText, TEXT_PLAIN);
320       mp.addBodyPart(part);
321     }
322     if (bodyHTML != null) {
323       MimeBodyPart part = new MimeBodyPart();
324       part.setContent(bodyHTML, TEXT_HTML);
325       mp.addBodyPart(part);
326     }
327     message.setContent(mp);
328     message.saveChanges();
329     send(message);
330   }
331 
332   /**
333    * Process an array of recipients with the given {@link javax.mail.Message.RecipientType}
334    *
335    * @param message
336    *          The message to add the recipients to
337    * @param type
338    *          The type of recipient
339    * @param strAddresses
340    * @throws MessagingException
341    */
342   private static void addRecipients(MimeMessage message, RecipientType type, String... strAddresses)
343           throws MessagingException {
344     if (strAddresses != null) {
345       InternetAddress[] addresses = new InternetAddress[strAddresses.length];
346       for (int i = 0; i < strAddresses.length; i++)
347         addresses[i] = new InternetAddress(strAddresses[i]);
348       message.addRecipients(type, addresses);
349     }
350   }
351 
352 }