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.portal.util;
016    
017    import com.liferay.mail.model.FileAttachment;
018    import com.liferay.mail.service.MailServiceUtil;
019    import com.liferay.portal.kernel.exception.PortalException;
020    import com.liferay.portal.kernel.log.Log;
021    import com.liferay.portal.kernel.log.LogFactoryUtil;
022    import com.liferay.portal.kernel.mail.MailMessage;
023    import com.liferay.portal.kernel.mail.SMTPAccount;
024    import com.liferay.portal.kernel.messaging.DestinationNames;
025    import com.liferay.portal.kernel.messaging.MessageBusUtil;
026    import com.liferay.portal.kernel.util.ClassLoaderPool;
027    import com.liferay.portal.kernel.util.EscapableObject;
028    import com.liferay.portal.kernel.util.GetterUtil;
029    import com.liferay.portal.kernel.util.HtmlEscapableObject;
030    import com.liferay.portal.kernel.util.HtmlUtil;
031    import com.liferay.portal.kernel.util.LocaleUtil;
032    import com.liferay.portal.kernel.util.ObjectValuePair;
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    import com.liferay.portal.model.Company;
037    import com.liferay.portal.model.Group;
038    import com.liferay.portal.model.Subscription;
039    import com.liferay.portal.model.User;
040    import com.liferay.portal.security.permission.PermissionChecker;
041    import com.liferay.portal.security.permission.PermissionCheckerFactoryUtil;
042    import com.liferay.portal.service.CompanyLocalServiceUtil;
043    import com.liferay.portal.service.GroupLocalServiceUtil;
044    import com.liferay.portal.service.ServiceContext;
045    import com.liferay.portal.service.SubscriptionLocalServiceUtil;
046    import com.liferay.portal.service.UserLocalServiceUtil;
047    import com.liferay.portal.service.permission.SubscriptionPermissionUtil;
048    
049    import java.io.File;
050    import java.io.IOException;
051    import java.io.ObjectInputStream;
052    import java.io.ObjectOutputStream;
053    import java.io.Serializable;
054    
055    import java.util.ArrayList;
056    import java.util.HashMap;
057    import java.util.HashSet;
058    import java.util.List;
059    import java.util.Locale;
060    import java.util.Map;
061    import java.util.Set;
062    
063    import javax.mail.internet.InternetAddress;
064    
065    /**
066     * @author Brian Wing Shun Chan
067     * @author Mate Thurzo
068     * @author Raymond Aug??
069     */
070    public class SubscriptionSender implements Serializable {
071    
072            public void addFileAttachment(File file) {
073                    addFileAttachment(file, null);
074            }
075    
076            public void addFileAttachment(File file, String fileName) {
077                    if (file == null) {
078                            return;
079                    }
080    
081                    if (fileAttachments == null) {
082                            fileAttachments = new ArrayList<FileAttachment>();
083                    }
084    
085                    FileAttachment attachment = new FileAttachment(file, fileName);
086    
087                    fileAttachments.add(attachment);
088            }
089    
090            public void addPersistedSubscribers(String className, long classPK) {
091                    ObjectValuePair<String, Long> ovp = new ObjectValuePair<String, Long>(
092                            className, classPK);
093    
094                    _persistestedSubscribersOVPs.add(ovp);
095            }
096    
097            public void addRuntimeSubscribers(String toAddress, String toName) {
098                    ObjectValuePair<String, String> ovp =
099                            new ObjectValuePair<String, String>(toAddress, toName);
100    
101                    _runtimeSubscribersOVPs.add(ovp);
102            }
103    
104            public void flushNotifications() throws Exception {
105                    initialize();
106    
107                    Thread currentThread = Thread.currentThread();
108    
109                    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
110    
111                    try {
112                            if ((_classLoader != null) &&
113                                    (contextClassLoader != _classLoader)) {
114    
115                                    currentThread.setContextClassLoader(_classLoader);
116                            }
117    
118                            String inferredClassName = null;
119                            long inferredClassPK = 0;
120    
121                            if (!_persistestedSubscribersOVPs.isEmpty()) {
122                                    ObjectValuePair<String, Long> objectValuePair =
123                                            _persistestedSubscribersOVPs.get(
124                                                    _persistestedSubscribersOVPs.size() - 1);
125    
126                                    inferredClassName = objectValuePair.getKey();
127                                    inferredClassPK = objectValuePair.getValue();
128                            }
129    
130                            for (ObjectValuePair<String, Long> ovp :
131                                            _persistestedSubscribersOVPs) {
132    
133                                    String className = ovp.getKey();
134                                    long classPK = ovp.getValue();
135    
136                                    List<Subscription> subscriptions =
137                                            SubscriptionLocalServiceUtil.getSubscriptions(
138                                                    companyId, className, classPK);
139    
140                                    for (Subscription subscription : subscriptions) {
141                                            try {
142                                                    notifySubscriber(
143                                                            subscription, inferredClassName, inferredClassPK);
144                                            }
145                                            catch (PortalException pe) {
146                                                    _log.error(
147                                                            "Unable to process subscription: " + subscription);
148    
149                                                    continue;
150                                            }
151                                    }
152    
153                                    if (bulk) {
154                                            Locale locale = LocaleUtil.getDefault();
155    
156                                            InternetAddress to = new InternetAddress(
157                                                    replaceContent(replyToAddress, locale),
158                                                    replaceContent(replyToAddress, locale));
159    
160                                            sendEmail(to, locale);
161                                    }
162                            }
163    
164                            _persistestedSubscribersOVPs.clear();
165    
166                            for (ObjectValuePair<String, String> ovp :
167                                            _runtimeSubscribersOVPs) {
168    
169                                    String toAddress = ovp.getKey();
170    
171                                    if (Validator.isNull(toAddress)) {
172                                            continue;
173                                    }
174    
175                                    if (_sentEmailAddresses.contains(toAddress)) {
176                                            if (_log.isDebugEnabled()) {
177                                                    _log.debug(
178                                                            "Do not send a duplicate email to " + toAddress);
179                                            }
180    
181                                            continue;
182                                    }
183    
184                                    if (_log.isDebugEnabled()) {
185                                            _log.debug(
186                                                    "Add " + toAddress + " to the list of users who " +
187                                                            "have received an email");
188                                    }
189    
190                                    _sentEmailAddresses.add(toAddress);
191    
192                                    String toName = ovp.getValue();
193    
194                                    InternetAddress to = new InternetAddress(toAddress, toName);
195    
196                                    sendEmail(to, LocaleUtil.getDefault());
197                            }
198    
199                            _runtimeSubscribersOVPs.clear();
200                    }
201                    finally {
202                            if ((_classLoader != null) &&
203                                    (contextClassLoader != _classLoader)) {
204    
205                                    currentThread.setContextClassLoader(contextClassLoader);
206                            }
207                    }
208            }
209    
210            public void flushNotificationsAsync() {
211                    Thread currentThread = Thread.currentThread();
212    
213                    _classLoader = currentThread.getContextClassLoader();
214    
215                    MessageBusUtil.sendMessage(DestinationNames.SUBSCRIPTION_SENDER, this);
216            }
217    
218            public Object getContextAttribute(String key) {
219                    return _context.get(key);
220            }
221    
222            public String getMailId() {
223                    return this.mailId;
224            }
225    
226            public void initialize() throws Exception {
227                    if (_initialized) {
228                            return;
229                    }
230    
231                    _initialized = true;
232    
233                    Company company = CompanyLocalServiceUtil.getCompany(companyId);
234    
235                    setContextAttribute("[$COMPANY_ID$]", company.getCompanyId());
236                    setContextAttribute("[$COMPANY_MX$]", company.getMx());
237                    setContextAttribute("[$COMPANY_NAME$]", company.getName());
238                    setContextAttribute("[$PORTAL_URL$]", company.getPortalURL(groupId));
239    
240                    if (groupId > 0) {
241                            Group group = GroupLocalServiceUtil.getGroup(groupId);
242    
243                            setContextAttribute("[$SITE_NAME$]", group.getDescriptiveName());
244                    }
245    
246                    if ((userId > 0) && Validator.isNotNull(_contextUserPrefix)) {
247                            setContextAttribute(
248                                    "[$" + _contextUserPrefix + "_USER_ADDRESS$]",
249                                    PortalUtil.getUserEmailAddress(userId));
250                            setContextAttribute(
251                                    "[$" + _contextUserPrefix + "_USER_NAME$]",
252                                    PortalUtil.getUserName(userId, StringPool.BLANK));
253                    }
254    
255                    mailId = PortalUtil.getMailId(
256                            company.getMx(), _mailIdPopPortletPrefix, _mailIdIds);
257            }
258    
259            public void setBody(String body) {
260                    this.body = body;
261            }
262    
263            public void setBulk(boolean bulk) {
264                    this.bulk = bulk;
265            }
266    
267            public void setCompanyId(long companyId) {
268                    this.companyId = companyId;
269            }
270    
271            public void setContextAttribute(String key, EscapableObject<String> value) {
272                    _context.put(key, value);
273            }
274    
275            public void setContextAttribute(String key, Object value) {
276                    setContextAttribute(key, value, true);
277            }
278    
279            public void setContextAttribute(String key, Object value, boolean escape) {
280                    setContextAttribute(
281                            key,
282                            new HtmlEscapableObject<String>(String.valueOf(value), escape));
283            }
284    
285            public void setContextAttributes(Object... values) {
286                    for (int i = 0; i < values.length; i += 2) {
287                            setContextAttribute(String.valueOf(values[i]), values[i + 1]);
288                    }
289            }
290    
291            public void setContextUserPrefix(String contextUserPrefix) {
292                    _contextUserPrefix = contextUserPrefix;
293            }
294    
295            public void setFrom(String fromAddress, String fromName) {
296                    this.fromAddress = fromAddress;
297                    this.fromName = fromName;
298            }
299    
300            public void setGroupId(long groupId) {
301                    this.groupId = groupId;
302            }
303    
304            public void setHtmlFormat(boolean htmlFormat) {
305                    this.htmlFormat = htmlFormat;
306            }
307    
308            public void setInReplyTo(String inReplyTo) {
309                    this.inReplyTo = inReplyTo;
310            }
311    
312            public void setLocalizedBodyMap(Map<Locale, String> localizedBodyMap) {
313                    this.localizedBodyMap = localizedBodyMap;
314            }
315    
316            public void setLocalizedSubjectMap(
317                    Map<Locale, String> localizedSubjectMap) {
318    
319                    this.localizedSubjectMap = localizedSubjectMap;
320            }
321    
322            public void setMailId(String popPortletPrefix, Object... ids) {
323                    _mailIdPopPortletPrefix = popPortletPrefix;
324                    _mailIdIds = ids;
325            }
326    
327            public void setPortletId(String portletId) {
328                    this.portletId = portletId;
329            }
330    
331            public void setReplyToAddress(String replyToAddress) {
332                    this.replyToAddress = replyToAddress;
333            }
334    
335            /**
336             * @see com.liferay.portal.kernel.search.BaseIndexer#getSiteGroupId(long)
337             */
338            public void setScopeGroupId(long scopeGroupId) {
339                    try {
340                            Group group = GroupLocalServiceUtil.getGroup(scopeGroupId);
341    
342                            if (group.isLayout()) {
343                                    groupId = group.getParentGroupId();
344                            }
345                            else {
346                                    groupId = scopeGroupId;
347                            }
348                    }
349                    catch (Exception e) {
350                    }
351    
352                    this.scopeGroupId = scopeGroupId;
353            }
354    
355            public void setServiceContext(ServiceContext serviceContext) {
356                    this.serviceContext = serviceContext;
357            }
358    
359            public void setSMTPAccount(SMTPAccount smtpAccount) {
360                    this.smtpAccount = smtpAccount;
361            }
362    
363            public void setSubject(String subject) {
364                    this.subject = subject;
365            }
366    
367            public void setUserId(long userId) {
368                    this.userId = userId;
369            }
370    
371            protected void deleteSubscription(Subscription subscription)
372                    throws Exception {
373    
374                    SubscriptionLocalServiceUtil.deleteSubscription(
375                            subscription.getSubscriptionId());
376            }
377    
378            protected boolean hasPermission(
379                            Subscription subscription, String inferredClassName,
380                            long inferredClassPK, User user)
381                    throws Exception {
382    
383                    PermissionChecker permissionChecker =
384                            PermissionCheckerFactoryUtil.create(user);
385    
386                    return SubscriptionPermissionUtil.contains(
387                            permissionChecker, subscription.getClassName(),
388                            subscription.getClassPK(), inferredClassName, inferredClassPK);
389            }
390    
391            protected void notifySubscriber(
392                            Subscription subscription, String inferredClassName,
393                            long inferredClassPK)
394                    throws Exception {
395    
396                    User user = UserLocalServiceUtil.fetchUserById(
397                            subscription.getUserId());
398    
399                    if (user == null) {
400                            if (_log.isInfoEnabled()) {
401                                    _log.info(
402                                            "Subscription " + subscription.getSubscriptionId() +
403                                                    " is stale and will be deleted");
404                            }
405    
406                            deleteSubscription(subscription);
407    
408                            return;
409                    }
410    
411                    String emailAddress = user.getEmailAddress();
412    
413                    if (_sentEmailAddresses.contains(emailAddress)) {
414                            if (_log.isDebugEnabled()) {
415                                    _log.debug("Do not send a duplicate email to " + emailAddress);
416                            }
417    
418                            return;
419                    }
420                    else {
421                            if (_log.isDebugEnabled()) {
422                                    _log.debug(
423                                            "Add " + emailAddress +
424                                                    " to the list of users who have received an email");
425                            }
426    
427                            _sentEmailAddresses.add(emailAddress);
428                    }
429    
430                    if (!user.isActive()) {
431                            if (_log.isDebugEnabled()) {
432                                    _log.debug("Skip inactive user " + user.getUserId());
433                            }
434    
435                            return;
436                    }
437    
438                    try {
439                            if (!hasPermission(
440                                            subscription, inferredClassName, inferredClassPK, user)) {
441    
442                                    if (_log.isDebugEnabled()) {
443                                            _log.debug("Skip unauthorized user " + user.getUserId());
444                                    }
445    
446                                    return;
447                            }
448                    }
449                    catch (Exception e) {
450                            _log.error(e, e);
451    
452                            return;
453                    }
454    
455                    if (bulk) {
456                            InternetAddress bulkAddress = new InternetAddress(
457                                    user.getEmailAddress(), user.getFullName());
458    
459                            if (_bulkAddresses == null) {
460                                    _bulkAddresses = new ArrayList<InternetAddress>();
461                            }
462    
463                            _bulkAddresses.add(bulkAddress);
464                    }
465                    else {
466                            try {
467                                    InternetAddress to = new InternetAddress(
468                                            user.getEmailAddress(), user.getFullName());
469    
470                                    sendEmail(to, user.getLocale());
471                            }
472                            catch (Exception e) {
473                                    _log.error(e, e);
474                            }
475                    }
476            }
477    
478            protected void processMailMessage(MailMessage mailMessage, Locale locale)
479                    throws Exception {
480    
481                    InternetAddress from = mailMessage.getFrom();
482                    InternetAddress to = mailMessage.getTo()[0];
483    
484                    String processedSubject = StringUtil.replace(
485                            mailMessage.getSubject(),
486                            new String[] {
487                                    "[$FROM_ADDRESS$]", "[$FROM_NAME$]", "[$TO_ADDRESS$]",
488                                    "[$TO_NAME$]"
489                            },
490                            new String[] {
491                                    from.getAddress(),
492                                    GetterUtil.getString(from.getPersonal(), from.getAddress()),
493                                    HtmlUtil.escape(to.getAddress()),
494                                    HtmlUtil.escape(
495                                            GetterUtil.getString(to.getPersonal(), to.getAddress()))
496                            });
497    
498                    processedSubject = replaceContent(processedSubject, locale, false);
499    
500                    mailMessage.setSubject(processedSubject);
501    
502                    String processedBody = StringUtil.replace(
503                            mailMessage.getBody(),
504                            new String[] {
505                                    "[$FROM_ADDRESS$]", "[$FROM_NAME$]", "[$TO_ADDRESS$]",
506                                    "[$TO_NAME$]"
507                            },
508                            new String[] {
509                                    from.getAddress(),
510                                    GetterUtil.getString(from.getPersonal(), from.getAddress()),
511                                    HtmlUtil.escape(to.getAddress()),
512                                    HtmlUtil.escape(
513                                            GetterUtil.getString(to.getPersonal(), to.getAddress()))
514                            });
515    
516                    processedBody = replaceContent(processedBody, locale, htmlFormat);
517    
518                    mailMessage.setBody(processedBody);
519            }
520    
521            protected String replaceContent(String content, Locale locale)
522                    throws Exception {
523    
524                    return replaceContent(content, locale, true);
525            }
526    
527            protected String replaceContent(
528                            String content, Locale locale, boolean escape)
529                    throws Exception {
530    
531                    for (Map.Entry<String, EscapableObject<String>> entry :
532                                    _context.entrySet()) {
533    
534                            String key = entry.getKey();
535                            EscapableObject<String> value = entry.getValue();
536    
537                            String valueString = null;
538    
539                            if (escape) {
540                                    valueString = value.getEscapedValue();
541                            }
542                            else {
543                                    valueString = value.getOriginalValue();
544                            }
545    
546                            content = StringUtil.replace(content, key, valueString);
547                    }
548    
549                    if (Validator.isNotNull(portletId)) {
550                            String portletName = PortalUtil.getPortletTitle(portletId, locale);
551    
552                            content = StringUtil.replace(
553                                    content, "[$PORTLET_NAME$]", portletName);
554                    }
555    
556                    Company company = CompanyLocalServiceUtil.getCompany(companyId);
557    
558                    content = StringUtil.replace(
559                            content,
560                            new String[] {
561                                    "href=\"/", "src=\"/"
562                            },
563                            new String[] {
564                                    "href=\"" + company.getPortalURL(groupId) + "/",
565                                    "src=\"" + company.getPortalURL(groupId) + "/"
566                            });
567    
568                    return content;
569            }
570    
571            protected void sendEmail(InternetAddress to, Locale locale)
572                    throws Exception {
573    
574                    InternetAddress from = new InternetAddress(
575                            replaceContent(fromAddress, locale),
576                            replaceContent(fromName, locale));
577    
578                    String processedSubject = null;
579    
580                    if (localizedSubjectMap != null) {
581                            String localizedSubject = localizedSubjectMap.get(locale);
582    
583                            if (Validator.isNull(localizedSubject)) {
584                                    Locale defaultLocale = LocaleUtil.getDefault();
585    
586                                    processedSubject = localizedSubjectMap.get(defaultLocale);
587                            }
588                            else {
589                                    processedSubject = localizedSubject;
590                            }
591                    }
592                    else {
593                            processedSubject = this.subject;
594                    }
595    
596                    String processedBody = null;
597    
598                    if (localizedBodyMap != null) {
599                            String localizedBody = localizedBodyMap.get(locale);
600    
601                            if (Validator.isNull(localizedBody)) {
602                                    Locale defaultLocale = LocaleUtil.getDefault();
603    
604                                    processedBody = localizedBodyMap.get(defaultLocale);
605                            }
606                            else {
607                                    processedBody = localizedBody;
608                            }
609                    }
610                    else {
611                            processedBody = this.body;
612                    }
613    
614                    MailMessage mailMessage = new MailMessage(
615                            from, to, processedSubject, processedBody, htmlFormat);
616    
617                    if (fileAttachments != null) {
618                            for (FileAttachment fileAttachment : fileAttachments) {
619                                    mailMessage.addFileAttachment(
620                                            fileAttachment.getFile(), fileAttachment.getFileName());
621                            }
622                    }
623    
624                    if (bulk && (_bulkAddresses != null)) {
625                            mailMessage.setBulkAddresses(
626                                    _bulkAddresses.toArray(
627                                            new InternetAddress[_bulkAddresses.size()]));
628    
629                            _bulkAddresses.clear();
630                    }
631    
632                    if (inReplyTo != null) {
633                            mailMessage.setInReplyTo(inReplyTo);
634                    }
635    
636                    mailMessage.setMessageId(mailId);
637    
638                    if (replyToAddress != null) {
639                            InternetAddress replyTo = new InternetAddress(
640                                    replaceContent(replyToAddress, locale),
641                                    replaceContent(replyToAddress, locale));
642    
643                            mailMessage.setReplyTo(new InternetAddress[] {replyTo});
644                    }
645    
646                    if (smtpAccount != null) {
647                            mailMessage.setSMTPAccount(smtpAccount);
648                    }
649    
650                    processMailMessage(mailMessage, locale);
651    
652                    MailServiceUtil.sendEmail(mailMessage);
653            }
654    
655            protected String body;
656            protected boolean bulk;
657            protected long companyId;
658            protected List<FileAttachment> fileAttachments =
659                    new ArrayList<FileAttachment>();
660            protected String fromAddress;
661            protected String fromName;
662            protected long groupId;
663            protected boolean htmlFormat;
664            protected String inReplyTo;
665            protected Map<Locale, String> localizedBodyMap;
666            protected Map<Locale, String> localizedSubjectMap;
667            protected String mailId;
668            protected String portletId;
669            protected String replyToAddress;
670            protected long scopeGroupId;
671            protected ServiceContext serviceContext;
672            protected SMTPAccount smtpAccount;
673            protected String subject;
674            protected long userId;
675    
676            private void readObject(ObjectInputStream objectInputStream)
677                    throws ClassNotFoundException, IOException {
678    
679                    objectInputStream.defaultReadObject();
680    
681                    String servletContextName = objectInputStream.readUTF();
682    
683                    if (!servletContextName.isEmpty()) {
684                            _classLoader = ClassLoaderPool.getClassLoader(servletContextName);
685                    }
686            }
687    
688            private void writeObject(ObjectOutputStream objectOutputStream)
689                    throws IOException {
690    
691                    objectOutputStream.defaultWriteObject();
692    
693                    String servletContextName = StringPool.BLANK;
694    
695                    if (_classLoader != null) {
696                            servletContextName = ClassLoaderPool.getContextName(_classLoader);
697                    }
698    
699                    objectOutputStream.writeUTF(servletContextName);
700            }
701    
702            private static Log _log = LogFactoryUtil.getLog(SubscriptionSender.class);
703    
704            private List<InternetAddress> _bulkAddresses;
705            private transient ClassLoader _classLoader;
706            private Map<String, EscapableObject<String>> _context =
707                    new HashMap<String, EscapableObject<String>>();
708            private String _contextUserPrefix;
709            private boolean _initialized;
710            private Object[] _mailIdIds;
711            private String _mailIdPopPortletPrefix;
712            private List<ObjectValuePair<String, Long>> _persistestedSubscribersOVPs =
713                    new ArrayList<ObjectValuePair<String, Long>>();
714            private List<ObjectValuePair<String, String>> _runtimeSubscribersOVPs =
715                    new ArrayList<ObjectValuePair<String, String>>();
716            private Set<String> _sentEmailAddresses = new HashSet<String>();
717    
718    }