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