001/* 002 * Copyright 2014 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util.mail; 017 018import java.io.File; 019import java.io.IOException; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Date; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Properties; 026import java.util.StringTokenizer; 027import java.util.concurrent.ExecutorService; 028import java.util.concurrent.Executors; 029import java.util.concurrent.ThreadFactory; 030import java.util.regex.Matcher; 031import java.util.regex.Pattern; 032 033import javax.mail.Message; 034import javax.mail.MessagingException; 035import javax.mail.Multipart; 036import javax.mail.Session; 037import javax.mail.Transport; 038import javax.mail.internet.InternetAddress; 039import javax.mail.internet.MimeBodyPart; 040import javax.mail.internet.MimeMessage; 041import javax.mail.internet.MimeMultipart; 042 043import org.apache.avalon.framework.activity.Disposable; 044import org.apache.avalon.framework.logger.AbstractLogEnabled; 045import org.apache.commons.lang3.StringUtils; 046import org.jsoup.Jsoup; 047import org.jsoup.nodes.Document; 048import org.jsoup.nodes.Element; 049import org.jsoup.select.Elements; 050import org.jsoup.select.Selector; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import org.ametys.runtime.config.Config; 055 056/** 057 * Helper for sending mail 058 */ 059public final class SendMailHelper extends AbstractLogEnabled implements Disposable 060{ 061 /** Logger */ 062 protected static final Logger _LOGGER = LoggerFactory.getLogger(SendMailHelper.class); 063 064 /** Attribute selectors pattern for CSS specificity processing */ 065 protected static final Pattern __CSS_SPECIFICITY_ATTR_PATTERN = Pattern.compile("(\\[[^\\]]+\\])"); 066 /** ID selectors pattern for CSS specificity processing */ 067 protected static final Pattern __CSS_SPECIFICITY_ID_PATTERN = Pattern.compile("(#[^\\s\\+>~\\.\\[:]+)"); 068 /** Class selectors pattern for CSS specificity processing */ 069 protected static final Pattern __CSS_SPECIFICITY_CLASS_PATTERN = Pattern.compile("(\\.[^\\s\\+>~\\.\\[:]+)"); 070 /** Pseudo-element selectors pattern for CSS specificity processing */ 071 protected static final Pattern __CSS_SPECIFICITY_PSEUDO_ELEMENT_PATTERN = Pattern.compile("(::[^\\s\\+>~\\.\\[:]+|:first-line|:first-letter|:before|:after)", Pattern.CASE_INSENSITIVE); 072 /** Pseudo-class (with bracket) selectors pattern for CSS specificity processing */ 073 protected static final Pattern __CSS_SPECIFICITY_PSEUDO_CLASS_WITH_BRACKETS_PATTERN = Pattern.compile("(:[\\w-]+\\([^\\)]*\\))", Pattern.CASE_INSENSITIVE); 074 /** Pseudo-class selectors pattern for CSS specificity processing */ 075 protected static final Pattern __CSS_SPECIFICITY_PSEUDO_CLASS_PATTERN = Pattern.compile("(:[^\\s\\+>~\\.\\[:]+)"); 076 /** Element selectors pattern for CSS specificity processing */ 077 protected static final Pattern __CSS_SPECIFICITY_ELEMENT_PATTERN = Pattern.compile("([^\\s\\+>~\\.\\[:]+)"); 078 079 /** Specific :not pseudo-class selectors pattern for CSS specificity processing */ 080 protected static final Pattern __CSS_SPECIFICITY_PSEUDO_CLASS_NOT_PATTERN = Pattern.compile(":not\\(([^\\)]*)\\)"); 081 /** Universal and separator characters pattern for CSS specificity processing */ 082 protected static final Pattern __CSS_SPECIFICITY_UNIVERSAL_AND_SEPARATOR_PATTERN = Pattern.compile("[\\*\\s\\+>~]"); 083 084 private static final ExecutorService __SINGLE_THREAD_EXECUTOR = Executors.newSingleThreadExecutor(new MailSenderThreadFactory()); 085 086 private SendMailHelper () 087 { 088 // Nothing 089 } 090 091 /** 092 * Sends mail without authentication or attachments. 093 * @param subject The mail subject 094 * @param htmlBody The HTML mail body. Can be null. 095 * @param textBody The text mail body. Can be null. 096 * @param recipient The recipient address 097 * @param sender The sender address 098 * @throws MessagingException If an error occurred while preparing or sending email 099 */ 100 public static void sendMail(String subject, String htmlBody, String textBody, String recipient, String sender) throws MessagingException 101 { 102 sendMail(subject, htmlBody, textBody, recipient, sender, false); 103 } 104 105 /** 106 * Sends mail without authentication or attachments. 107 * @param subject The mail subject 108 * @param htmlBody The HTML mail body. Can be null. 109 * @param textBody The text mail body. Can be null. 110 * @param recipient The recipient address 111 * @param sender The sender address 112 * @param async True to use asynchronous mail sending 113 * @throws MessagingException If an error occurred while preparing or sending email 114 */ 115 public static void sendMail(String subject, String htmlBody, String textBody, String recipient, String sender, boolean async) throws MessagingException 116 { 117 String smtpHost = Config.getInstance().getValueAsString("smtp.mail.host"); 118 long smtpPort = Config.getInstance().getValueAsLong("smtp.mail.port"); 119 String securityProtocol = Config.getInstance().getValueAsString("smtp.mail.security.protocol"); 120 121 sendMail(subject, htmlBody, textBody, recipient, sender, smtpHost, smtpPort, securityProtocol, async); 122 } 123 124 /** 125 * Sends mail without authentication or attachments. 126 * @param subject The mail subject 127 * @param htmlBody The HTML mail body. Can be null. 128 * @param textBody The text mail body. Can be null. 129 * @param recipient The recipient address 130 * @param sender The sender address 131 * @param host The server mail host 132 * @param port The server mail port 133 * @param securityProtocol The server mail security protocol 134 * @param async True to use asynchronous mail sending 135 * @throws MessagingException If an error occurred while preparing or sending email 136 */ 137 public static void sendMail(String subject, String htmlBody, String textBody, String recipient, String sender, String host, long port, String securityProtocol, boolean async) throws MessagingException 138 { 139 String user = Config.getInstance().getValueAsString("smtp.mail.user"); 140 String password = Config.getInstance().getValueAsString("smtp.mail.password"); 141 sendMail(subject, htmlBody, textBody, recipient, sender, host, port, securityProtocol, user, password, async); 142 } 143 144 145 /** 146 * Sends mail with authentication, without attachments. 147 * @param subject The mail subject 148 * @param htmlBody The HTML mail body. Can be null. 149 * @param textBody The text mail body. Can be null. 150 * @param recipient The recipient address 151 * @param sender The sender address 152 * @param host The server mail host 153 * @param port The server port 154 * @param securityProtocol The server mail security protocol 155 * @param user The user name 156 * @param password The user password 157 * @param async True to use asynchronous mail sending 158 * @throws MessagingException If an error occurred while preparing or sending email 159 */ 160 public static void sendMail(String subject, String htmlBody, String textBody, String recipient, String sender, String host, long port, String securityProtocol, String user, String password, boolean async) throws MessagingException 161 { 162 try 163 { 164 String sp = StringUtils.defaultIfEmpty(securityProtocol, Config.getInstance().getValueAsString("smtp.mail.security.protocol")); 165 sendMail(subject, htmlBody, textBody, null, recipient, sender, null, null, false, false, host, port, sp, user, password, async); 166 } 167 catch (IOException e) 168 { 169 // Should never happen, as IOException can only be thrown where there are attachments. 170 _LOGGER.error("Cannot send mail " + subject + " to " + recipient, e); 171 } 172 } 173 174 /** 175 * Sends mail without authentication, with attachments. 176 * @param subject The mail subject 177 * @param htmlBody The HTML mail body. Can be null. 178 * @param textBody The text mail body. Can be null. 179 * @param attachments the file attachments. Can be null. 180 * @param recipient The recipient address 181 * @param sender The sender address 182 * @throws MessagingException If an error occurred while preparing or sending email 183 * @throws IOException if an error occurs while attaching a file. 184 */ 185 public static void sendMail(String subject, String htmlBody, String textBody, Collection<File> attachments, String recipient, String sender) throws MessagingException, IOException 186 { 187 sendMail(subject, htmlBody, textBody, attachments, recipient, sender, null, null, false); 188 } 189 190 /** 191 * Sends mail without authentication, with attachments. 192 * @param subject The mail subject 193 * @param htmlBody The HTML mail body. Can be null. 194 * @param textBody The text mail body. Can be null. 195 * @param attachments the file attachments. Can be null. 196 * @param recipient The recipient address 197 * @param sender The sender address 198 * @param cc Carbon copy address list. Can be null. 199 * @param bcc Blind carbon copy address list. Can be null. 200 * @param async True to use asynchronous mail sending 201 * @throws MessagingException If an error occurred while preparing or sending email 202 * @throws IOException if an error occurs while attaching a file. 203 */ 204 public static void sendMail(String subject, String htmlBody, String textBody, Collection<File> attachments, String recipient, String sender, List<String> cc, List<String> bcc, boolean async) throws MessagingException, IOException 205 { 206 sendMail(subject, htmlBody, textBody, attachments, recipient, sender, cc, bcc, false, false, async); 207 } 208 209 /** 210 * Sends mail without authentication, with attachments. 211 * @param subject The mail subject 212 * @param htmlBody The HTML mail body. Can be null. 213 * @param textBody The text mail body. Can be null. 214 * @param attachments the file attachments. Can be null. 215 * @param recipient The recipient address 216 * @param sender The sender address 217 * @param cc Carbon copy address list. Can be null. 218 * @param bcc Blind carbon copy address list. Can be null. 219 * @param deliveryReceipt true to request that the receiving mail server send a notification when the mail is received. 220 * @param readReceipt true to request that the receiving mail client send a notification when the person opens the mail. 221 * @param async True to use asynchronous mail sending 222 * @throws MessagingException If an error occurred while preparing or sending email 223 * @throws IOException if an error occurs while attaching a file. 224 */ 225 public static void sendMail(String subject, String htmlBody, String textBody, Collection<File> attachments, String recipient, String sender, List<String> cc, List<String> bcc, boolean deliveryReceipt, boolean readReceipt, boolean async) throws MessagingException, IOException 226 { 227 String smtpHost = Config.getInstance().getValueAsString("smtp.mail.host"); 228 long smtpPort = Config.getInstance().getValueAsLong("smtp.mail.port"); 229 String protocol = Config.getInstance().getValueAsString("smtp.mail.security.protocol"); 230 231 sendMail(subject, htmlBody, textBody, attachments, recipient, sender, cc, bcc, deliveryReceipt, readReceipt, smtpHost, smtpPort, protocol, null, null, async); 232 } 233 234 /** 235 * Sends mail without authentication, with attachments. 236 * @param subject The mail subject 237 * @param htmlBody The HTML mail body. Can be null. 238 * @param textBody The text mail body. Can be null. 239 * @param attachments the file attachments. Can be null. 240 * @param recipient The recipient address 241 * @param sender The sender address 242 * @param cc Carbon copy address list. Can be null. 243 * @param bcc Blind carbon copy address list. Can be null. 244 * @param deliveryReceipt true to request that the receiving mail server send a notification when the mail is received. 245 * @param readReceipt true to request that the receiving mail client send a notification when the person opens the mail. 246 * @param host The server mail host 247 * @param port The server port 248 * @param securityProtocol The server mail security protocol 249 * @param async True to use asynchronous mail sending 250 * @throws MessagingException If an error occurred while preparing or sending email 251 * @throws IOException if an error occurs while attaching a file. 252 */ 253 public static void sendMail(String subject, String htmlBody, String textBody, Collection<File> attachments, String recipient, String sender, List<String> cc, List<String> bcc, boolean deliveryReceipt, boolean readReceipt, String host, long port, String securityProtocol, boolean async) throws MessagingException, IOException 254 { 255 String protocol = Config.getInstance().getValueAsString("smtp.mail.security.protocol"); 256 sendMail(subject, htmlBody, textBody, attachments, recipient, sender, cc, bcc, deliveryReceipt, readReceipt, host, port, protocol, null, null, async); 257 } 258 259 /** 260 * Sends mail with authentication and attachments. 261 * @param subject The mail subject 262 * @param htmlBody The HTML mail body. Can be null. 263 * @param textBody The text mail body. Can be null. 264 * @param attachments the file attachments. Can be null. 265 * @param recipient The recipient address 266 * @param sender The sender address. Can be null when called by MailChecker. 267 * @param cc Carbon copy address list. Can be null. 268 * @param bcc Blind carbon copy address list. Can be null. 269 * @param deliveryReceipt true to request that the receiving mail server send a notification when the mail is received. 270 * @param readReceipt true to request that the receiving mail client send a notification when the person opens the mail. 271 * @param host The server mail host. Can be null when called by MailChecker. 272 * @param securityProtocol the security protocol to use when transporting the email 273 * @param port The server port 274 * @param user The user name 275 * @param password The user password 276 * @param async True to use asynchronous mail sending 277 * @throws MessagingException If an error occurred while preparing or sending email 278 * @throws IOException if an error occurs while attaching a file. 279 */ 280 public static void sendMail(String subject, String htmlBody, String textBody, Collection<File> attachments, String recipient, String sender, List<String> cc, List<String> bcc, boolean deliveryReceipt, boolean readReceipt, String host, long port, String securityProtocol, String user, String password, boolean async) throws MessagingException, IOException 281 { 282 MailSender mailSender = new MailSender(_LOGGER, subject, htmlBody, textBody, attachments, recipient, sender, cc, bcc, deliveryReceipt, readReceipt, host, port, securityProtocol, user, password); 283 284 if (!async) 285 { 286 mailSender.sendMail(); 287 } 288 else 289 { 290 __SINGLE_THREAD_EXECUTOR.execute(mailSender); 291 } 292 } 293 294 /** 295 * This method inline css in <style> tags directly in the appropriates tags. e.g. : <style>h1 {color: red;}</style> <h1>a</h1> becomes <h1 style="color: red">a</h1> 296 * @param html The initial non null html 297 * @return The inlined html 298 */ 299 public static String inlineCSS(String html) 300 { 301 List<CssRule> rules = new LinkedList<>(); 302 303 Document doc = Jsoup.parse(html); 304 Elements els = doc.select("style"); 305 306 for (Element e : els) 307 { 308 String styleRules = e.getAllElements().get(0).data(); 309 styleRules = styleRules.replaceAll("\t|\n", "").replaceAll("<!--", "").replaceAll("-->", ""); 310 311 styleRules = _removeComments(styleRules); 312 313 styleRules = styleRules.trim(); 314 315 StringTokenizer st = new StringTokenizer(styleRules, "{}"); 316 while (st.countTokens() > 1) 317 { 318 String selectors = st.nextToken(); 319 String properties = st.nextToken(); 320 321 String[] selector = selectors.split(","); 322 for (String s : selector) 323 { 324 if (StringUtils.isNotBlank(s)) 325 { 326 rules.add(new CssRule(s, properties, rules.size())); 327 } 328 } 329 } 330 e.remove(); 331 } 332 333 // Sort rules by specificity 334 Collections.sort(rules, Collections.reverseOrder()); 335 336 for (CssRule rule : rules) 337 { 338 try 339 { 340 Elements selectedElements = doc.select(rule.getSelector()); 341 for (Element selElem : selectedElements) 342 { 343 String oldProperties = selElem.attr("style"); 344 selElem.attr("style", oldProperties.length() > 0 ? concatenateProperties(oldProperties, rule.getProperties()) : rule.getProperties()); 345 } 346 } 347 catch (Selector.SelectorParseException ex) 348 { 349 _LOGGER.error("Cannot inline CSS. Ignoring this rule and continuing.", ex); 350 } 351 } 352 353 return doc.toString(); 354 } 355 356 private static String _removeComments(String styleRules) 357 { 358 int i = styleRules.indexOf("/*"); 359 int j = styleRules.indexOf("*/"); 360 361 if (i >= 0 && j > i) 362 { 363 return styleRules.substring(0, i) + _removeComments(styleRules.substring(j + 2)); 364 } 365 366 return styleRules; 367 } 368 369 private static String concatenateProperties(String oldProp, String newProp) 370 { 371 String between = ""; 372 if (!newProp.endsWith(";")) 373 { 374 between += ";"; 375 } 376 return newProp + between + oldProp.trim(); // The existing (old) properties should take precedence. 377 } 378 379 @Override 380 public void dispose() 381 { 382 __SINGLE_THREAD_EXECUTOR.shutdownNow(); 383 } 384 385 private static class CssRule implements Comparable<CssRule> 386 { 387 private String _selector; 388 private String _properties; 389 private CssSpecificity _specificity; 390 391 /** 392 * CSSRule constructor 393 * @param selector css selector 394 * @param properties css properties for this rule 395 * @param positionIdx The rules declaration index 396 */ 397 public CssRule(String selector, String properties, int positionIdx) 398 { 399 _selector = selector; 400 _properties = properties; 401 _specificity = new CssSpecificity(_selector, positionIdx); 402 } 403 404 /** 405 * Selector getter 406 * @return the selector 407 */ 408 public String getSelector() 409 { 410 return _selector; 411 } 412 413 /** 414 * Properties getter 415 * @return the properties 416 */ 417 public String getProperties() 418 { 419 return _properties; 420 } 421 422 public int compareTo(CssRule r) 423 { 424 return _specificity.compareTo(r._specificity); 425 } 426 } 427 428 private static class CssSpecificity implements Comparable<CssSpecificity> 429 { 430 private int[] _weights; 431 432 public CssSpecificity(String selector, int positionIdx) 433 { 434 // Position index is used to differentiate equality cases 435 // -> latest declaration should be the one applied 436 _weights = new int[]{0, 0, 0, 0, positionIdx}; 437 438 String input = selector; 439 440 // This part is loosely based on https://github.com/keeganstreet/specificity 441 442 // Remove :not pseudo-class but leave its argument 443 input = __CSS_SPECIFICITY_PSEUDO_CLASS_NOT_PATTERN.matcher(input).replaceAll(" $1 "); 444 445 // The following regular expressions assume that selectors matching the preceding regular expressions have been removed 446 input = _countReplaceAll(__CSS_SPECIFICITY_ATTR_PATTERN, input, 2); 447 input = _countReplaceAll(__CSS_SPECIFICITY_ID_PATTERN, input, 1); 448 input = _countReplaceAll(__CSS_SPECIFICITY_CLASS_PATTERN, input, 2); 449 input = _countReplaceAll(__CSS_SPECIFICITY_PSEUDO_ELEMENT_PATTERN, input, 3); 450 // A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang() 451 input = _countReplaceAll(__CSS_SPECIFICITY_PSEUDO_CLASS_WITH_BRACKETS_PATTERN, input, 2); 452 // A regex for other pseudo classes, which don't have brackets 453 input = _countReplaceAll(__CSS_SPECIFICITY_PSEUDO_CLASS_PATTERN, input, 2); 454 455 // Remove universal selector and separator characters 456 input = __CSS_SPECIFICITY_UNIVERSAL_AND_SEPARATOR_PATTERN.matcher(input).replaceAll(" "); 457 458 _countReplaceAll(__CSS_SPECIFICITY_ELEMENT_PATTERN, input, 3); 459 } 460 461 private String _countReplaceAll(Pattern pattern, String selector, int sIndex) 462 { 463 Matcher m = pattern.matcher(selector); 464 StringBuffer sb = new StringBuffer(); 465 466 while (m.find()) 467 { 468 // Increment desired weight counter 469 _weights[sIndex]++; 470 471 // Replace matched selector part with whitespace 472 m.appendReplacement(sb, " "); 473 } 474 475 m.appendTail(sb); 476 477 return sb.toString(); 478 } 479 480 public int compareTo(CssSpecificity o) 481 { 482 for (int i = 0; i < _weights.length; i++) 483 { 484 if (_weights[i] != o._weights[i]) 485 { 486 return _weights[i] - o._weights[i]; 487 } 488 } 489 490 return 0; 491 } 492 } 493 494 private static class MailSender implements Runnable 495 { 496 private String _subject; 497 private String _htmlBody; 498 private String _textBody; 499 private Collection<File> _attachments; 500 private String _recipient; 501 private String _sender; 502 private List<String> _cc; 503 private List<String> _bcc; 504 private boolean _deliveryReceipt; 505 private boolean _readReceipt; 506 private String _host; 507 private long _port; 508 private String _securityProtocol; 509 private String _user; 510 private String _password; 511 private Logger _logger; 512 513 /** 514 * Initialize the mail sender with email parameters 515 * @param logger The logger 516 * @param subject The mail subject 517 * @param htmlBody The HTML mail body. Can be null. 518 * @param textBody The text mail body. Can be null. 519 * @param attachments the file attachments. Can be null. 520 * @param recipient The recipient address 521 * @param sender The sender address. Can be null when called by MailChecker. 522 * @param cc Carbon copy address list. Can be null. 523 * @param bcc Blind carbon copy address list. Can be null. 524 * @param deliveryReceipt true to request that the receiving mail server send a notification when the mail is received. 525 * @param readReceipt true to request that the receiving mail client send a notification when the person opens the mail. 526 * @param host The server mail host. Can be null when called by MailChecker. 527 * @param securityProtocol the security protocol to use when transporting the email 528 * @param port The server port 529 * @param user The user name 530 * @param password The user password 531 */ 532 public MailSender(Logger logger, String subject, String htmlBody, String textBody, Collection<File> attachments, String recipient, String sender, List<String> cc, List<String> bcc, boolean deliveryReceipt, boolean readReceipt, String host, long port, String securityProtocol, String user, String password) 533 { 534 _logger = logger; 535 _subject = subject; 536 _htmlBody = htmlBody; 537 _textBody = textBody; 538 _attachments = attachments; 539 _recipient = recipient; 540 _sender = sender; 541 _cc = cc; 542 _bcc = bcc; 543 _deliveryReceipt = deliveryReceipt; 544 _readReceipt = readReceipt; 545 _host = host; 546 _port = port; 547 _securityProtocol = securityProtocol; 548 _user = user; 549 _password = password; 550 } 551 552 public void run() 553 { 554 try 555 { 556 sendMail(); 557 } 558 catch (Exception e) 559 { 560 _logger.error("Unable to send mail: " + _subject + "", e); 561 } 562 } 563 564 public void sendMail() throws MessagingException, IOException 565 { 566 Properties props = new Properties(); 567 568 // Setup mail server 569 props.put("mail.smtp.host", _host); 570 props.put("mail.smtp.port", _port); 571 572 // Security protocol 573 if (_securityProtocol.equals("starttls")) 574 { 575 props.put("mail.smtp.starttls.enable", "true"); 576 } 577 else if (_securityProtocol.equals("tlsssl")) 578 { 579 props.put("mail.smtp.ssl.enable", "true"); 580 } 581 582 Session session = Session.getInstance(props, null); 583 584 // Define message 585 MimeMessage message = new MimeMessage(session); 586 587 if (_sender != null) 588 { 589 message.setFrom(new InternetAddress(_sender)); 590 } 591 592 message.setSentDate(new Date()); 593 message.setSubject(_subject); 594 595 // Root multipart 596 Multipart multipart = new MimeMultipart("mixed"); 597 598 // Message body part. 599 Multipart messageMultipart = new MimeMultipart("alternative"); 600 MimeBodyPart messagePart = new MimeBodyPart(); 601 messagePart.setContent(messageMultipart); 602 multipart.addBodyPart(messagePart); 603 604 if (_textBody != null) 605 { 606 MimeBodyPart textBodyPart = new MimeBodyPart(); 607 textBodyPart.setContent(_textBody, "text/plain;charset=utf-8"); 608 textBodyPart.addHeader("Content-Type", "text/plain;charset=utf-8"); 609 messageMultipart.addBodyPart(textBodyPart); 610 } 611 612 if (_htmlBody != null) 613 { 614 MimeBodyPart htmlBodyPart = new MimeBodyPart(); 615 htmlBodyPart.setContent(inlineCSS(_htmlBody), "text/html;charset=utf-8"); 616 htmlBodyPart.addHeader("Content-Type", "text/html;charset=utf-8"); 617 messageMultipart.addBodyPart(htmlBodyPart); 618 } 619 620 if (_attachments != null) 621 { 622 for (File attachment : _attachments) 623 { 624 MimeBodyPart fileBodyPart = new MimeBodyPart(); 625 fileBodyPart.attachFile(attachment); 626 multipart.addBodyPart(fileBodyPart); 627 } 628 } 629 message.setContent(multipart); 630 631 // Recipients 632 if (_recipient != null) 633 { 634 message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(_recipient, false)); 635 } 636 637 // Carbon copies 638 if (_cc != null) 639 { 640 message.setRecipients(Message.RecipientType.CC, InternetAddress.parse(StringUtils.join(_cc, ','), false)); 641 } 642 643 // Blind carbon copies 644 if (_bcc != null) 645 { 646 message.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(StringUtils.join(_bcc, ','), false)); 647 } 648 649 // Delivery receipt : Return-Receipt-To 650 if (_deliveryReceipt) 651 { 652 message.setHeader("Return-Receipt-To", _sender); 653 } 654 655 // Read receipt : Disposition-Notification-To 656 if (_readReceipt) 657 { 658 message.setHeader("Disposition-Notification-To", _sender); 659 } 660 661 message.saveChanges(); 662 663 Transport tr = session.getTransport("smtp"); 664 665 try 666 { 667 tr.connect(_host, (int) _port, StringUtils.trimToNull(_user), StringUtils.trimToNull(_password)); 668 669 if (_recipient != null && _sender != null) 670 { 671 tr.sendMessage(message, message.getAllRecipients()); 672 } 673 } 674 finally 675 { 676 tr.close(); 677 } 678 } 679 } 680 681 private static class MailSenderThreadFactory implements ThreadFactory 682 { 683 private ThreadFactory _defaultThreadFactory; 684 685 public MailSenderThreadFactory() 686 { 687 _defaultThreadFactory = Executors.defaultThreadFactory(); 688 } 689 690 public Thread newThread(Runnable r) 691 { 692 Thread thread = _defaultThreadFactory.newThread(r); 693 thread.setName("mail-sender-thread"); 694 thread.setDaemon(true); 695 696 return thread; 697 } 698 } 699}