001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.util.mail;
016    
017    import com.liferay.mail.model.FileAttachment;
018    import com.liferay.mail.service.MailServiceUtil;
019    import com.liferay.portal.kernel.exception.SystemException;
020    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayInputStream;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.log.LogUtil;
024    import com.liferay.portal.kernel.mail.Account;
025    import com.liferay.portal.kernel.mail.MailMessage;
026    import com.liferay.portal.kernel.mail.SMTPAccount;
027    import com.liferay.portal.kernel.util.ArrayUtil;
028    import com.liferay.portal.kernel.util.GetterUtil;
029    import com.liferay.portal.kernel.util.InfrastructureUtil;
030    import com.liferay.portal.kernel.util.PropsKeys;
031    import com.liferay.portal.kernel.util.PropsUtil;
032    import com.liferay.portal.kernel.util.StringPool;
033    import com.liferay.portal.kernel.util.StringUtil;
034    import com.liferay.portal.kernel.util.Validator;
035    
036    import java.io.File;
037    
038    import java.net.SocketException;
039    
040    import java.util.Arrays;
041    import java.util.Date;
042    import java.util.List;
043    import java.util.Properties;
044    
045    import javax.activation.DataHandler;
046    import javax.activation.DataSource;
047    import javax.activation.FileDataSource;
048    
049    import javax.mail.Address;
050    import javax.mail.Message;
051    import javax.mail.MessagingException;
052    import javax.mail.Part;
053    import javax.mail.SendFailedException;
054    import javax.mail.Session;
055    import javax.mail.Transport;
056    import javax.mail.internet.AddressException;
057    import javax.mail.internet.InternetAddress;
058    import javax.mail.internet.MimeBodyPart;
059    import javax.mail.internet.MimeMessage;
060    import javax.mail.internet.MimeMultipart;
061    
062    /**
063     * @author Brian Wing Shun Chan
064     * @author Brian Myunghun Kim
065     * @author Jorge Ferrer
066     * @author Neil Griffin
067     * @author Thiago Moreira
068     * @author Brett Swaim
069     */
070    public class MailEngine {
071    
072            public static Session getSession() {
073                    return getSession(false);
074            }
075    
076            public static Session getSession(Account account) {
077                    Properties properties = _getProperties(account);
078    
079                    Session session = Session.getInstance(properties);
080    
081                    if (_log.isDebugEnabled()) {
082                            session.setDebug(true);
083    
084                            session.getProperties().list(System.out);
085                    }
086    
087                    return session;
088            }
089    
090            public static Session getSession(boolean cache) {
091                    Session session = null;
092    
093                    try {
094                            session = MailServiceUtil.getSession();
095                    }
096                    catch (SystemException se) {
097                            if (_log.isWarnEnabled()) {
098                                    _log.warn(se, se);
099                            }
100    
101                            session = InfrastructureUtil.getMailSession();
102                    }
103    
104                    if (_log.isDebugEnabled()) {
105                            session.setDebug(true);
106    
107                            session.getProperties().list(System.out);
108                    }
109    
110                    return session;
111            }
112    
113            public static void send(byte[] bytes) throws MailEngineException {
114                    try {
115                            Session session = getSession();
116    
117                            Message message = new MimeMessage(
118                                    session, new UnsyncByteArrayInputStream(bytes));
119    
120                            _send(session, message, null, _BATCH_SIZE);
121                    }
122                    catch (Exception e) {
123                            throw new MailEngineException(e);
124                    }
125            }
126    
127            public static void send(
128                            InternetAddress from, InternetAddress to, String subject,
129                            String body)
130                    throws MailEngineException {
131    
132                    send(
133                            from, new InternetAddress[] {to}, null, null, subject, body, false,
134                            null, null, null);
135            }
136    
137            public static void send(
138                            InternetAddress from, InternetAddress to, String subject,
139                            String body, boolean htmlFormat)
140                    throws MailEngineException {
141    
142                    send(
143                            from, new InternetAddress[] {to}, null, null, subject, body,
144                            htmlFormat, null, null, null);
145            }
146    
147            public static void send(
148                            InternetAddress from, InternetAddress[] to, InternetAddress[] cc,
149                            InternetAddress[] bcc, InternetAddress[] bulkAddresses,
150                            String subject, String body, boolean htmlFormat,
151                            InternetAddress[] replyTo, String messageId, String inReplyTo)
152                    throws MailEngineException {
153    
154                    send(
155                            from, to, cc, bcc, bulkAddresses, subject, body, htmlFormat,
156                            replyTo, messageId, inReplyTo, null);
157            }
158    
159            public static void send(
160                            InternetAddress from, InternetAddress[] to, InternetAddress[] cc,
161                            InternetAddress[] bcc, InternetAddress[] bulkAddresses,
162                            String subject, String body, boolean htmlFormat,
163                            InternetAddress[] replyTo, String messageId, String inReplyTo,
164                            List<FileAttachment> fileAttachments)
165                    throws MailEngineException {
166    
167                    send(
168                            from, to, cc, bcc, bulkAddresses, subject, body, htmlFormat,
169                            replyTo, messageId, inReplyTo, fileAttachments, null);
170            }
171    
172            public static void send(
173                            InternetAddress from, InternetAddress[] to, InternetAddress[] cc,
174                            InternetAddress[] bcc, InternetAddress[] bulkAddresses,
175                            String subject, String body, boolean htmlFormat,
176                            InternetAddress[] replyTo, String messageId, String inReplyTo,
177                            List<FileAttachment> fileAttachments, SMTPAccount smtpAccount)
178                    throws MailEngineException {
179    
180                    long startTime = System.currentTimeMillis();
181    
182                    if (_log.isDebugEnabled()) {
183                            _log.debug("From: " + from);
184                            _log.debug("To: " + Arrays.toString(to));
185                            _log.debug("CC: " + Arrays.toString(cc));
186                            _log.debug("BCC: " + Arrays.toString(bcc));
187                            _log.debug("List Addresses: " + Arrays.toString(bulkAddresses));
188                            _log.debug("Subject: " + subject);
189                            _log.debug("Body: " + body);
190                            _log.debug("HTML Format: " + htmlFormat);
191                            _log.debug("Reply to: " + Arrays.toString(replyTo));
192                            _log.debug("Message ID: " + messageId);
193                            _log.debug("In Reply To: " + inReplyTo);
194    
195                            if ((fileAttachments != null) && _log.isDebugEnabled()) {
196                                    for (int i = 0; i < fileAttachments.size(); i++) {
197                                            FileAttachment fileAttachment = fileAttachments.get(i);
198    
199                                            File file = fileAttachment.getFile();
200    
201                                            if (file == null) {
202                                                    continue;
203                                            }
204    
205                                            _log.debug(
206                                                    "Attachment " + i + " file " + file.getAbsolutePath() +
207                                                            " and file name " + fileAttachment.getFileName());
208                                    }
209                            }
210                    }
211    
212                    try {
213                            InternetAddressUtil.validateAddress(from);
214    
215                            if (ArrayUtil.isNotEmpty(to)) {
216                                    InternetAddressUtil.validateAddresses(to);
217                            }
218    
219                            if (ArrayUtil.isNotEmpty(cc)) {
220                                    InternetAddressUtil.validateAddresses(cc);
221                            }
222    
223                            if (ArrayUtil.isNotEmpty(bcc)) {
224                                    InternetAddressUtil.validateAddresses(bcc);
225                            }
226    
227                            if (ArrayUtil.isNotEmpty(replyTo)) {
228                                    InternetAddressUtil.validateAddresses(replyTo);
229                            }
230    
231                            if (ArrayUtil.isNotEmpty(bulkAddresses)) {
232                                    InternetAddressUtil.validateAddresses(bulkAddresses);
233                            }
234    
235                            Session session = null;
236    
237                            if (smtpAccount == null) {
238                                    session = getSession();
239                            }
240                            else {
241                                    session = getSession(smtpAccount);
242                            }
243    
244                            Message message = new LiferayMimeMessage(session);
245    
246                            message.addHeader(
247                                    "X-Auto-Response-Suppress", "AutoReply, DR, NDR, NRN, OOF, RN");
248    
249                            message.setFrom(from);
250    
251                            if (ArrayUtil.isNotEmpty(to)) {
252                                    message.setRecipients(Message.RecipientType.TO, to);
253                            }
254    
255                            if (ArrayUtil.isNotEmpty(cc)) {
256                                    message.setRecipients(Message.RecipientType.CC, cc);
257                            }
258    
259                            if (ArrayUtil.isNotEmpty(bcc)) {
260                                    message.setRecipients(Message.RecipientType.BCC, bcc);
261                            }
262    
263                            subject = GetterUtil.getString(subject);
264    
265                            message.setSubject(_sanitizeCRLF(subject));
266    
267                            if ((fileAttachments != null) && (fileAttachments.size() > 0)) {
268                                    MimeMultipart rootMultipart = new MimeMultipart(
269                                            _MULTIPART_TYPE_MIXED);
270    
271                                    MimeMultipart messageMultipart = new MimeMultipart(
272                                            _MULTIPART_TYPE_ALTERNATIVE);
273    
274                                    MimeBodyPart messageBodyPart = new MimeBodyPart();
275    
276                                    messageBodyPart.setContent(messageMultipart);
277    
278                                    rootMultipart.addBodyPart(messageBodyPart);
279    
280                                    if (htmlFormat) {
281                                            MimeBodyPart bodyPart = new MimeBodyPart();
282    
283                                            bodyPart.setContent(body, _TEXT_HTML);
284    
285                                            messageMultipart.addBodyPart(bodyPart);
286                                    }
287                                    else {
288                                            MimeBodyPart bodyPart = new MimeBodyPart();
289    
290                                            bodyPart.setText(body);
291    
292                                            messageMultipart.addBodyPart(bodyPart);
293                                    }
294    
295                                    for (int i = 0; i < fileAttachments.size(); i++) {
296                                            FileAttachment fileAttachment = fileAttachments.get(i);
297    
298                                            File file = fileAttachment.getFile();
299    
300                                            if (file == null) {
301                                                    continue;
302                                            }
303    
304                                            MimeBodyPart mimeBodyPart = new MimeBodyPart();
305    
306                                            DataSource dataSource = new FileDataSource(file);
307    
308                                            mimeBodyPart.setDataHandler(new DataHandler(dataSource));
309                                            mimeBodyPart.setDisposition(Part.ATTACHMENT);
310    
311                                            if (fileAttachment.getFileName() != null) {
312                                                    mimeBodyPart.setFileName(fileAttachment.getFileName());
313                                            }
314                                            else {
315                                                    mimeBodyPart.setFileName(file.getName());
316                                            }
317    
318                                            rootMultipart.addBodyPart(mimeBodyPart);
319                                    }
320    
321                                    message.setContent(rootMultipart);
322    
323                                    message.saveChanges();
324                            }
325                            else {
326                                    if (htmlFormat) {
327                                            message.setContent(body, _TEXT_HTML);
328                                    }
329                                    else {
330                                            message.setContent(body, _TEXT_PLAIN);
331                                    }
332                            }
333    
334                            message.setSentDate(new Date());
335    
336                            if (ArrayUtil.isNotEmpty(replyTo)) {
337                                    message.setReplyTo(replyTo);
338                            }
339    
340                            if (messageId != null) {
341                                    message.setHeader("Message-ID", _sanitizeCRLF(messageId));
342                            }
343    
344                            if (inReplyTo != null) {
345                                    message.setHeader("In-Reply-To", _sanitizeCRLF(inReplyTo));
346                                    message.setHeader("References", _sanitizeCRLF(inReplyTo));
347                            }
348    
349                            int batchSize = GetterUtil.getInteger(
350                                    PropsUtil.get(PropsKeys.MAIL_BATCH_SIZE), _BATCH_SIZE);
351    
352                            _send(session, message, bulkAddresses, batchSize);
353                    }
354                    catch (SendFailedException sfe) {
355                            _log.error(sfe);
356    
357                            if (_isThrowsExceptionOnFailure()) {
358                                    throw new MailEngineException(sfe);
359                            }
360                    }
361                    catch (Exception e) {
362                            throw new MailEngineException(e);
363                    }
364    
365                    if (_log.isDebugEnabled()) {
366                            _log.debug(
367                                    "Sending mail takes " +
368                                            (System.currentTimeMillis() - startTime) + " ms");
369                    }
370            }
371    
372            public static void send(
373                            InternetAddress from, InternetAddress[] to, InternetAddress[] cc,
374                            InternetAddress[] bcc, String subject, String body)
375                    throws MailEngineException {
376    
377                    send(from, to, cc, bcc, subject, body, false, null, null, null);
378            }
379    
380            public static void send(
381                            InternetAddress from, InternetAddress[] to, InternetAddress[] cc,
382                            InternetAddress[] bcc, String subject, String body,
383                            boolean htmlFormat, InternetAddress[] replyTo, String messageId,
384                            String inReplyTo)
385                    throws MailEngineException {
386    
387                    send(
388                            from, to, cc, bcc, null, subject, body, htmlFormat, replyTo,
389                            messageId, inReplyTo, null);
390            }
391    
392            public static void send(
393                            InternetAddress from, InternetAddress[] to, InternetAddress[] cc,
394                            String subject, String body)
395                    throws MailEngineException {
396    
397                    send(from, to, cc, null, subject, body, false, null, null, null);
398            }
399    
400            public static void send(
401                            InternetAddress from, InternetAddress[] to, InternetAddress[] cc,
402                            String subject, String body, boolean htmlFormat)
403                    throws MailEngineException {
404    
405                    send(from, to, cc, null, subject, body, htmlFormat, null, null, null);
406            }
407    
408            public static void send(
409                            InternetAddress from, InternetAddress[] to, String subject,
410                            String body)
411                    throws MailEngineException {
412    
413                    send(from, to, null, null, subject, body, false, null, null, null);
414            }
415    
416            public static void send(
417                            InternetAddress from, InternetAddress[] to, String subject,
418                            String body, boolean htmlFormat)
419                    throws MailEngineException {
420    
421                    send(from, to, null, null, subject, body, htmlFormat, null, null, null);
422            }
423    
424            public static void send(MailMessage mailMessage)
425                    throws MailEngineException {
426    
427                    send(
428                            mailMessage.getFrom(), mailMessage.getTo(), mailMessage.getCC(),
429                            mailMessage.getBCC(), mailMessage.getBulkAddresses(),
430                            mailMessage.getSubject(), mailMessage.getBody(),
431                            mailMessage.isHTMLFormat(), mailMessage.getReplyTo(),
432                            mailMessage.getMessageId(), mailMessage.getInReplyTo(),
433                            mailMessage.getFileAttachments(), mailMessage.getSMTPAccount());
434            }
435    
436            public static void send(String from, String to, String subject, String body)
437                    throws MailEngineException {
438    
439                    try {
440                            send(
441                                    new InternetAddress(from), new InternetAddress(to), subject,
442                                    body);
443                    }
444                    catch (AddressException ae) {
445                            throw new MailEngineException(ae);
446                    }
447            }
448    
449            private static Address[] _getBatchAddresses(
450                    Address[] addresses, int index, int batchSize) {
451    
452                    if ((batchSize == _BATCH_SIZE) && (index == 0)) {
453                            return addresses;
454                    }
455                    else if (batchSize == _BATCH_SIZE) {
456                            return null;
457                    }
458    
459                    int start = index * batchSize;
460    
461                    if (start > addresses.length) {
462                            return null;
463                    }
464    
465                    int end = ((index + 1) * batchSize);
466    
467                    if (end > addresses.length) {
468                            end = addresses.length;
469                    }
470    
471                    return ArrayUtil.subset(addresses, start, end);
472            }
473    
474            private static Properties _getProperties(Account account) {
475                    Properties properties = new Properties();
476    
477                    String protocol = account.getProtocol();
478    
479                    properties.setProperty("mail.transport.protocol", protocol);
480                    properties.setProperty("mail." + protocol + ".host", account.getHost());
481                    properties.setProperty(
482                            "mail." + protocol + ".port", String.valueOf(account.getPort()));
483    
484                    if (account.isRequiresAuthentication()) {
485                            properties.setProperty("mail." + protocol + ".auth", "true");
486                            properties.setProperty(
487                                    "mail." + protocol + ".user", account.getUser());
488                            properties.setProperty(
489                                    "mail." + protocol + ".password", account.getPassword());
490                    }
491    
492                    if (account.isSecure()) {
493                            properties.setProperty(
494                                    "mail." + protocol + ".socketFactory.class",
495                                    "javax.net.ssl.SSLSocketFactory");
496                            properties.setProperty(
497                                    "mail." + protocol + ".socketFactory.fallback", "false");
498                            properties.setProperty(
499                                    "mail." + protocol + ".socketFactory.port",
500                                    String.valueOf(account.getPort()));
501                    }
502    
503                    return properties;
504            }
505    
506            private static String _getSMTPProperty(Session session, String suffix) {
507                    String protocol = GetterUtil.getString(
508                            session.getProperty("mail.transport.protocol"));
509    
510                    if (protocol.equals(Account.PROTOCOL_SMTPS)) {
511                            return session.getProperty("mail.smtps." + suffix);
512                    }
513                    else {
514                            return session.getProperty("mail.smtp." + suffix);
515                    }
516            }
517    
518            private static boolean _isThrowsExceptionOnFailure() {
519                    return GetterUtil.getBoolean(
520                            PropsUtil.get(PropsKeys.MAIL_THROWS_EXCEPTION_ON_FAILURE));
521            }
522    
523            private static String _sanitizeCRLF(String text) {
524                    return StringUtil.replace(
525                            text, new String[] {StringPool.NEW_LINE, StringPool.RETURN},
526                            new String[] {StringPool.SPACE, StringPool.SPACE});
527            }
528    
529            private static void _send(
530                            Session session, Message message, InternetAddress[] bulkAddresses,
531                            int batchSize)
532                    throws MailEngineException {
533    
534                    try {
535                            boolean smtpAuth = GetterUtil.getBoolean(
536                                    _getSMTPProperty(session, "auth"), false);
537                            String smtpHost = _getSMTPProperty(session, "host");
538                            int smtpPort = GetterUtil.getInteger(
539                                    _getSMTPProperty(session, "port"), Account.PORT_SMTP);
540                            String user = _getSMTPProperty(session, "user");
541                            String password = _getSMTPProperty(session, "password");
542    
543                            if (smtpAuth && Validator.isNotNull(user) &&
544                                    Validator.isNotNull(password)) {
545    
546                                    String protocol = GetterUtil.getString(
547                                            session.getProperty("mail.transport.protocol"),
548                                            Account.PROTOCOL_SMTP);
549    
550                                    Transport transport = session.getTransport(protocol);
551    
552                                    transport.connect(smtpHost, smtpPort, user, password);
553    
554                                    Address[] addresses = null;
555    
556                                    if (ArrayUtil.isNotEmpty(bulkAddresses)) {
557                                            addresses = bulkAddresses;
558                                    }
559                                    else {
560                                            addresses = message.getAllRecipients();
561                                    }
562    
563                                    for (int i = 0;; i++) {
564                                            Address[] batchAddresses = _getBatchAddresses(
565                                                    addresses, i, batchSize);
566    
567                                            if (ArrayUtil.isEmpty(batchAddresses)) {
568                                                    break;
569                                            }
570    
571                                            transport.sendMessage(message, batchAddresses);
572                                    }
573    
574                                    transport.close();
575                            }
576                            else {
577                                    if (ArrayUtil.isNotEmpty(bulkAddresses)) {
578                                            int curBatch = 0;
579    
580                                            Address[] portion = _getBatchAddresses(
581                                                    bulkAddresses, curBatch, batchSize);
582    
583                                            while (ArrayUtil.isNotEmpty(portion)) {
584                                                    Transport.send(message, portion);
585    
586                                                    curBatch++;
587    
588                                                    portion = _getBatchAddresses(
589                                                            bulkAddresses, curBatch, batchSize);
590                                            }
591                                    }
592                                    else {
593                                            Transport.send(message);
594                                    }
595                            }
596                    }
597                    catch (MessagingException me) {
598                            if (me.getNextException() instanceof SocketException) {
599                                    if (_log.isWarnEnabled()) {
600                                            _log.warn(
601                                                    "Failed to connect to a valid mail server. Please " +
602                                                            "make sure one is properly configured. " +
603                                                                    me.getMessage());
604                                    }
605                            }
606                            else {
607                                    LogUtil.log(_log, me);
608                            }
609    
610                            if (_isThrowsExceptionOnFailure()) {
611                                    throw new MailEngineException(me);
612                            }
613                    }
614            }
615    
616            private static final int _BATCH_SIZE = 0;
617    
618            private static final String _MULTIPART_TYPE_ALTERNATIVE = "alternative";
619    
620            private static final String _MULTIPART_TYPE_MIXED = "mixed";
621    
622            private static final String _TEXT_HTML = "text/html;charset=\"UTF-8\"";
623    
624            private static final String _TEXT_PLAIN = "text/plain;charset=\"UTF-8\"";
625    
626            private static Log _log = LogFactoryUtil.getLog(MailEngine.class);
627    
628    }