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