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