001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
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.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    /**
070     * @author Brian Wing Shun Chan
071     * @author Mate Thurzo
072     * @author Raymond Aug??
073     */
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            /**
359             * @see com.liferay.portal.kernel.search.BaseIndexer#getSiteGroupId(long)
360             */
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            /**
419             * @deprecated As of 6.2.0, replaced by {@link #hasPermission(Subscription,
420             *             String, long, User)}
421             */
422            protected boolean hasPermission(Subscription subscription, User user)
423                    throws Exception {
424    
425                    return hasPermission(subscription, null, 0, user);
426            }
427    
428            /**
429             * @deprecated As of 6.2.0, replaced by {@link
430             *             #notifySubscriber(Subscription, String, long)}
431             */
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    }