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