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