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.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 {@link
320             *      com.liferay.portal.kernel.search.BaseIndexer#getParentGroupId(long)}
321             */
322            public void setScopeGroupId(long scopeGroupId) {
323                    try {
324                            Group group = GroupLocalServiceUtil.getGroup(scopeGroupId);
325    
326                            if (group.isLayout()) {
327                                    groupId = group.getParentGroupId();
328                            }
329                            else {
330                                    groupId = scopeGroupId;
331                            }
332                    }
333                    catch (Exception e) {
334                    }
335    
336                    this.scopeGroupId = scopeGroupId;
337            }
338    
339            public void setServiceContext(ServiceContext serviceContext) {
340                    this.serviceContext = serviceContext;
341            }
342    
343            public void setSMTPAccount(SMTPAccount smtpAccount) {
344                    this.smtpAccount = smtpAccount;
345            }
346    
347            public void setSubject(String subject) {
348                    this.subject = subject;
349            }
350    
351            public void setUserId(long userId) {
352                    this.userId = userId;
353            }
354    
355            protected void deleteSubscription(Subscription subscription)
356                    throws Exception {
357    
358                    SubscriptionLocalServiceUtil.deleteSubscription(
359                            subscription.getSubscriptionId());
360            }
361    
362            protected boolean hasPermission(Subscription subscription, User user)
363                    throws Exception {
364    
365                    PermissionChecker permissionChecker =
366                            PermissionCheckerFactoryUtil.create(user);
367    
368                    return SubscriptionPermissionUtil.contains(
369                            permissionChecker, subscription.getClassName(),
370                            subscription.getClassPK());
371            }
372    
373            protected void notifySubscriber(Subscription subscription)
374                    throws Exception {
375    
376                    User user = null;
377    
378                    try {
379                            user = UserLocalServiceUtil.getUserById(subscription.getUserId());
380                    }
381                    catch (NoSuchUserException nsue) {
382                            if (_log.isInfoEnabled()) {
383                                    _log.info(
384                                            "Subscription " + subscription.getSubscriptionId() +
385                                                    " is stale and will be deleted");
386                            }
387    
388                            deleteSubscription(subscription);
389    
390                            return;
391                    }
392    
393                    String emailAddress = user.getEmailAddress();
394    
395                    if (_sentEmailAddresses.contains(emailAddress)) {
396                            if (_log.isDebugEnabled()) {
397                                    _log.debug("Do not send a duplicate email to " + emailAddress);
398                            }
399    
400                            return;
401                    }
402                    else {
403                            if (_log.isDebugEnabled()) {
404                                    _log.debug(
405                                            "Add " + emailAddress +
406                                                    " to the list of users who have received an email");
407                            }
408    
409                            _sentEmailAddresses.add(emailAddress);
410                    }
411    
412                    if (!user.isActive()) {
413                            if (_log.isDebugEnabled()) {
414                                    _log.debug("Skip inactive user " + user.getUserId());
415                            }
416    
417                            return;
418                    }
419    
420                    try {
421                            if (!hasPermission(subscription, user)) {
422                                    if (_log.isDebugEnabled()) {
423                                            _log.debug("Skip unauthorized user " + user.getUserId());
424                                    }
425    
426                                    return;
427                            }
428                    }
429                    catch (Exception e) {
430                            _log.error(e, e);
431    
432                            return;
433                    }
434    
435                    if (bulk) {
436                            InternetAddress bulkAddress = new InternetAddress(
437                                    user.getEmailAddress(), user.getFullName());
438    
439                            if (_bulkAddresses == null) {
440                                    _bulkAddresses = new ArrayList<InternetAddress>();
441                            }
442    
443                            _bulkAddresses.add(bulkAddress);
444                    }
445                    else {
446                            try {
447                                    InternetAddress to = new InternetAddress(
448                                            user.getEmailAddress(), user.getFullName());
449    
450                                    sendEmail(to, user.getLocale());
451                            }
452                            catch (Exception e) {
453                                    _log.error(e, e);
454                            }
455                    }
456            }
457    
458            protected void processMailMessage(MailMessage mailMessage, Locale locale)
459                    throws Exception {
460    
461                    InternetAddress from = mailMessage.getFrom();
462                    InternetAddress to = mailMessage.getTo()[0];
463    
464                    String processedSubject = StringUtil.replace(
465                            mailMessage.getSubject(),
466                            new String[] {
467                                    "[$FROM_ADDRESS$]", "[$FROM_NAME$]", "[$TO_ADDRESS$]",
468                                    "[$TO_NAME$]"
469                            },
470                            new String[] {
471                                    from.getAddress(),
472                                    GetterUtil.getString(from.getPersonal(), from.getAddress()),
473                                    HtmlUtil.escape(to.getAddress()),
474                                    HtmlUtil.escape(
475                                            GetterUtil.getString(to.getPersonal(), to.getAddress()))
476                            });
477    
478                    processedSubject = replaceContent(processedSubject, locale, false);
479    
480                    mailMessage.setSubject(processedSubject);
481    
482                    String processedBody = StringUtil.replace(
483                            mailMessage.getBody(),
484                            new String[] {
485                                    "[$FROM_ADDRESS$]", "[$FROM_NAME$]", "[$TO_ADDRESS$]",
486                                    "[$TO_NAME$]"
487                            },
488                            new String[] {
489                                    from.getAddress(),
490                                    GetterUtil.getString(from.getPersonal(), from.getAddress()),
491                                    HtmlUtil.escape(to.getAddress()),
492                                    HtmlUtil.escape(
493                                            GetterUtil.getString(to.getPersonal(), to.getAddress()))
494                            });
495    
496                    processedBody = replaceContent(processedBody, locale, htmlFormat);
497    
498                    mailMessage.setBody(processedBody);
499            }
500    
501            protected String replaceContent(String content, Locale locale)
502                    throws Exception {
503    
504                    return replaceContent(content, locale, true);
505            }
506    
507            protected String replaceContent(
508                            String content, Locale locale, boolean escape)
509                    throws Exception {
510    
511                    for (Map.Entry<String, EscapableObject<String>> entry :
512                                    _context.entrySet()) {
513    
514                            String key = entry.getKey();
515                            EscapableObject<String> value = entry.getValue();
516    
517                            String valueString = null;
518    
519                            if (escape) {
520                                    valueString = value.getEscapedValue();
521                            }
522                            else {
523                                    valueString = value.getOriginalValue();
524                            }
525    
526                            content = StringUtil.replace(content, key, valueString);
527                    }
528    
529                    if (Validator.isNotNull(portletId)) {
530                            String portletName = PortalUtil.getPortletTitle(portletId, locale);
531    
532                            content = StringUtil.replace(
533                                    content, "[$PORTLET_NAME$]", portletName);
534                    }
535    
536                    Company company = CompanyLocalServiceUtil.getCompany(companyId);
537    
538                    content = StringUtil.replace(
539                            content,
540                            new String[] {
541                                    "href=\"/", "src=\"/"
542                            },
543                            new String[] {
544                                    "href=\"" + company.getPortalURL(groupId) + "/",
545                                    "src=\"" + company.getPortalURL(groupId) + "/"
546                            });
547    
548                    return content;
549            }
550    
551            protected void sendEmail(InternetAddress to, Locale locale)
552                    throws Exception {
553    
554                    InternetAddress from = new InternetAddress(
555                            replaceContent(fromAddress, locale),
556                            replaceContent(fromName, locale));
557    
558                    String processedSubject = null;
559    
560                    if (localizedSubjectMap != null) {
561                            String localizedSubject = localizedSubjectMap.get(locale);
562    
563                            if (Validator.isNull(localizedSubject)) {
564                                    Locale defaultLocale = LocaleUtil.getDefault();
565    
566                                    processedSubject = localizedSubjectMap.get(defaultLocale);
567                            }
568                            else {
569                                    processedSubject = localizedSubject;
570                            }
571                    }
572                    else {
573                            processedSubject = this.subject;
574                    }
575    
576                    String processedBody = null;
577    
578                    if (localizedBodyMap != null) {
579                            String localizedBody = localizedBodyMap.get(locale);
580    
581                            if (Validator.isNull(localizedBody)) {
582                                    Locale defaultLocale = LocaleUtil.getDefault();
583    
584                                    processedBody = localizedBodyMap.get(defaultLocale);
585                            }
586                            else {
587                                    processedBody = localizedBody;
588                            }
589                    }
590                    else {
591                            processedBody = this.body;
592                    }
593    
594                    MailMessage mailMessage = new MailMessage(
595                            from, to, processedSubject, processedBody, htmlFormat);
596    
597                    if (fileAttachments != null) {
598                            for (FileAttachment fileAttachment : fileAttachments) {
599                                    mailMessage.addFileAttachment(
600                                            fileAttachment.getFile(), fileAttachment.getFileName());
601                            }
602                    }
603    
604                    if (bulk && (_bulkAddresses != null)) {
605                            mailMessage.setBulkAddresses(
606                                    _bulkAddresses.toArray(
607                                            new InternetAddress[_bulkAddresses.size()]));
608    
609                            _bulkAddresses.clear();
610                    }
611    
612                    if (inReplyTo != null) {
613                            mailMessage.setInReplyTo(inReplyTo);
614                    }
615    
616                    mailMessage.setMessageId(mailId);
617    
618                    if (replyToAddress != null) {
619                            InternetAddress replyTo = new InternetAddress(
620                                    replaceContent(replyToAddress, locale),
621                                    replaceContent(replyToAddress, locale));
622    
623                            mailMessage.setReplyTo(new InternetAddress[] {replyTo});
624                    }
625    
626                    if (smtpAccount != null) {
627                            mailMessage.setSMTPAccount(smtpAccount);
628                    }
629    
630                    processMailMessage(mailMessage, locale);
631    
632                    MailServiceUtil.sendEmail(mailMessage);
633            }
634    
635            protected String body;
636            protected boolean bulk;
637            protected long companyId;
638            protected List<FileAttachment> fileAttachments =
639                    new ArrayList<FileAttachment>();
640            protected String fromAddress;
641            protected String fromName;
642            protected long groupId;
643            protected boolean htmlFormat;
644            protected String inReplyTo;
645            protected Map<Locale, String> localizedBodyMap;
646            protected Map<Locale, String> localizedSubjectMap;
647            protected String mailId;
648            protected String portletId;
649            protected String replyToAddress;
650            protected long scopeGroupId;
651            protected ServiceContext serviceContext;
652            protected SMTPAccount smtpAccount;
653            protected String subject;
654            protected long userId;
655    
656            private static Log _log = LogFactoryUtil.getLog(SubscriptionSender.class);
657    
658            private List<InternetAddress> _bulkAddresses;
659            private ClassLoader _classLoader;
660            private Map<String, EscapableObject<String>> _context =
661                    new HashMap<String, EscapableObject<String>>();
662            private String _contextUserPrefix;
663            private boolean _initialized;
664            private Object[] _mailIdIds;
665            private String _mailIdPopPortletPrefix;
666            private List<ObjectValuePair<String, Long>> _persistestedSubscribersOVPs =
667                    new ArrayList<ObjectValuePair<String, Long>>();
668            private List<ObjectValuePair<String, String>> _runtimeSubscribersOVPs =
669                    new ArrayList<ObjectValuePair<String, String>>();
670            private Set<String> _sentEmailAddresses = new HashSet<String>();
671    
672    }