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