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