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