use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.
the class ToXML method encodeInviteComponent.
public static Element encodeInviteComponent(Element parent, ItemIdFormatter ifmt, OperationContext octxt, CalendarItem calItem, /* may be null */
ItemId calId, /* may be null */
Invite invite, int fields, boolean neuter) throws ServiceException {
boolean allFields = true;
if (fields != NOTIFY_FIELDS) {
allFields = false;
if (!needToOutput(fields, Change.INVITE)) {
return parent;
}
}
Element e = parent.addElement(MailConstants.E_INVITE_COMPONENT);
e.addAttribute(MailConstants.A_CAL_METHOD, invite.getMethod());
e.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, invite.getComponentNum());
e.addAttribute(MailConstants.A_CAL_RSVP, invite.getRsvp());
boolean allowPrivateAccess = calItem != null ? allowPrivateAccess(octxt, calItem) : true;
if (allFields) {
if (invite.isPublic() || allowPrivateAccess) {
String priority = invite.getPriority();
if (priority != null) {
e.addAttribute(MailConstants.A_CAL_PRIORITY, priority);
}
e.addAttribute(MailConstants.A_NAME, invite.getName());
e.addAttribute(MailConstants.A_CAL_LOCATION, invite.getLocation());
List<String> categories = invite.getCategories();
if (categories != null) {
for (String cat : categories) {
e.addElement(MailConstants.E_CAL_CATEGORY).setText(cat);
}
}
List<String> comments = invite.getComments();
if (comments != null) {
for (String cmt : comments) {
e.addElement(MailConstants.E_CAL_COMMENT).setText(cmt);
}
}
List<String> contacts = invite.getContacts();
if (contacts != null) {
for (String contact : contacts) {
e.addElement(MailConstants.E_CAL_CONTACT).setText(contact);
}
}
Geo geo = invite.getGeo();
if (geo != null) {
geo.toXml(e);
}
// Percent Complete (VTODO)
if (invite.isTodo()) {
String pct = invite.getPercentComplete();
if (pct != null)
e.addAttribute(MailConstants.A_TASK_PERCENT_COMPLETE, pct);
long completed = invite.getCompleted();
if (completed != 0) {
ParsedDateTime c = ParsedDateTime.fromUTCTime(completed);
e.addAttribute(MailConstants.A_TASK_COMPLETED, c.getDateTimePartString());
}
}
// Attendee(s)
List<ZAttendee> attendees = invite.getAttendees();
for (ZAttendee at : attendees) {
at.toXml(e);
}
// Alarms
Iterator<Alarm> alarmsIter = invite.alarmsIterator();
while (alarmsIter.hasNext()) {
Alarm alarm = alarmsIter.next();
alarm.toXml(e);
}
// x-prop
encodeXProps(e, invite.xpropsIterator());
// fragment
String fragment = invite.getFragment();
if (!Strings.isNullOrEmpty(fragment)) {
e.addAttribute(MailConstants.E_FRAG, fragment, Element.Disposition.CONTENT);
}
if (!invite.hasBlobPart()) {
e.addAttribute(MailConstants.A_CAL_NO_BLOB, true);
}
// Description (plain and html)
String desc = invite.getDescription();
if (desc != null) {
Element descElem = e.addElement(MailConstants.E_CAL_DESCRIPTION);
descElem.setText(desc);
}
String descHtml = invite.getDescriptionHtml();
BrowserDefang defanger = DefangFactory.getDefanger(MimeConstants.CT_TEXT_HTML);
if (descHtml != null) {
try {
descHtml = StringUtil.stripControlCharacters(descHtml);
descHtml = defanger.defang(descHtml, neuter);
Element descHtmlElem = e.addElement(MailConstants.E_CAL_DESC_HTML);
descHtmlElem.setText(descHtml);
} catch (IOException ex) {
ZimbraLog.calendar.warn("Unable to defang HTML for SetAppointmentRequest", ex);
}
}
if (invite.isEvent()) {
if (calItem != null && calItem instanceof Appointment) {
Instance inst = Instance.fromInvite(calItem.getId(), invite);
Appointment appt = (Appointment) calItem;
e.addAttribute(MailConstants.A_APPT_FREEBUSY_ACTUAL, appt.getEffectiveFreeBusyActual(invite, inst));
}
e.addAttribute(MailConstants.A_APPT_FREEBUSY, invite.getFreeBusy());
e.addAttribute(MailConstants.A_APPT_TRANSPARENCY, invite.getTransparency());
}
// Organizer
if (invite.hasOrganizer()) {
ZOrganizer org = invite.getOrganizer();
org.toXml(e);
}
e.addAttribute(MailConstants.A_CAL_URL, invite.getUrl());
}
if (invite.isOrganizer()) {
e.addAttribute(MailConstants.A_CAL_ISORG, true);
}
boolean isRecurring = false;
e.addAttribute("x_uid", invite.getUid());
e.addAttribute(MailConstants.A_UID, invite.getUid());
e.addAttribute(MailConstants.A_CAL_SEQUENCE, invite.getSeqNo());
//zdsync
e.addAttribute(MailConstants.A_CAL_DATETIME, invite.getDTStamp());
String itemId = null;
if (calId != null) {
itemId = calId.toString(ifmt);
} else if (calItem != null) {
itemId = ifmt.formatItemId(calItem);
}
if ((itemId != null) && !("0".equals(itemId))) {
e.addAttribute(MailConstants.A_CAL_ID, /* calItemId */
itemId);
if (invite.isEvent()) {
// for backward compat
e.addAttribute(MailConstants.A_APPT_ID_DEPRECATE_ME, /* apptId */
itemId);
}
if (calItem != null) {
ItemId ciFolderId = new ItemId(calItem.getMailbox(), calItem.getFolderId());
e.addAttribute(MailConstants.A_CAL_ITEM_FOLDER, /* ciFolder */
ifmt.formatItemId(ciFolderId));
}
}
Recurrence.IRecurrence recur = invite.getRecurrence();
if (recur != null) {
isRecurring = true;
Element recurElt = e.addElement(MailConstants.E_CAL_RECUR);
recur.toXml(recurElt);
}
e.addAttribute(MailConstants.A_CAL_STATUS, invite.getStatus());
e.addAttribute(MailConstants.A_CAL_CLASS, invite.getClassProp());
boolean allDay = invite.isAllDayEvent();
boolean isException = invite.hasRecurId();
if (isException) {
e.addAttribute(MailConstants.A_CAL_IS_EXCEPTION, true);
RecurId rid = invite.getRecurId();
e.addAttribute(MailConstants.A_CAL_RECURRENCE_ID_Z, rid.getDtZ());
encodeRecurId(e, rid, allDay);
}
boolean forceUTC = DebugConfig.calendarForceUTC && !isRecurring && !isException && !allDay;
ParsedDateTime dtStart = invite.getStartTime();
if (dtStart != null) {
encodeDtStart(e, dtStart, allDay, forceUTC);
}
ParsedDateTime dtEnd = invite.getEndTime();
if (dtEnd != null) {
encodeDtEnd(e, dtEnd, allDay, invite.isTodo(), forceUTC);
}
ParsedDuration dur = invite.getDuration();
if (dur != null) {
dur.toXml(e);
}
if (allDay) {
e.addAttribute(MailConstants.A_CAL_ALLDAY, true);
}
if (invite.isDraft()) {
e.addAttribute(MailConstants.A_CAL_DRAFT, true);
}
if (invite.isNeverSent()) {
e.addAttribute(MailConstants.A_CAL_NEVER_SENT, true);
}
}
return e;
}
use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.
the class Appointment method processPartStat.
@Override
protected String processPartStat(Invite invite, MimeMessage mmInv, boolean forCreate, String defaultPartStat) throws ServiceException {
Mailbox mbox = getMailbox();
OperationContext octxt = mbox.getOperationContext();
CreateCalendarItemPlayer player = octxt != null ? (CreateCalendarItemPlayer) octxt.getPlayer() : null;
long opTime = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
Account account = getMailbox().getAccount();
boolean onBehalfOf = false;
Account authAcct = account;
if (octxt != null) {
Account authuser = octxt.getAuthenticatedUser();
if (authuser != null) {
onBehalfOf = !account.getId().equalsIgnoreCase(authuser.getId());
if (onBehalfOf)
authAcct = authuser;
}
}
boolean asAdmin = octxt != null ? octxt.isUsingAdminPrivileges() : false;
boolean allowPrivateAccess = allowPrivateAccess(authAcct, asAdmin);
String partStat = defaultPartStat;
if (player != null) {
String p = player.getCalendarItemPartStat();
if (p != null)
partStat = p;
}
// See if we have RSVP=FALSE for the attendee. Let's assume RSVP was requested unless it is
// explicitly set to FALSE.
boolean rsvpRequested = true;
ZAttendee attendee = invite.getMatchingAttendee(account);
if (attendee != null) {
Boolean rsvp = attendee.getRsvp();
if (rsvp != null)
rsvpRequested = rsvp.booleanValue();
}
RedoLogProvider redoProvider = RedoLogProvider.getInstance();
// Don't send reply emails if we're not on master (in redo-driven master/replica setup).
// Don't send reply emails if we're replaying redo for reasons other than crash recovery.
// In other words, we DO want to send emails during crash recovery, because we're completing
// an interrupted transaction. But we don't send emails during redo reply phase of restore.
// Also don't send emails for cancel invites. (Organizer doesn't expect reply for cancels.)
// And don't send emails for task requests.
// Don't send reply emails from a system account. (e.g. archiving, galsync, ham/spam)
boolean needReplyEmail = rsvpRequested && redoProvider.isMaster() && (player == null || redoProvider.getRedoLogManager().getInCrashRecovery()) && invite.hasOrganizer() && !invite.isCancel() && !invite.isTodo() && !account.isIsSystemResource();
if (invite.isOrganizer()) {
// Organizer always accepts.
partStat = IcalXmlStrMap.PARTSTAT_ACCEPTED;
} else if (account instanceof CalendarResource && octxt == null) {
// Auto accept/decline processing should only occur during email delivery. In particular,
// don't do it if we're here during ics import. We're in email delivery if octxt == null.
// (There needs to be a better way to determine that...)
boolean replySent = false;
CalendarResource resource = (CalendarResource) account;
Locale lc;
Account organizer = invite.getOrganizerAccount();
if (organizer != null)
lc = organizer.getLocale();
else
lc = resource.getLocale();
if (resource.autoAcceptDecline() || resource.autoDeclineIfBusy() || resource.autoDeclineRecurring()) {
boolean replyListUpdated = false;
// If auto-accept is enabled, assume it'll be accepted until it gets declined.
if (resource.autoAcceptDecline())
partStat = IcalXmlStrMap.PARTSTAT_ACCEPTED;
if (isRecurring() && resource.autoDeclineRecurring()) {
// Decline because resource is configured to decline all recurring appointments.
partStat = IcalXmlStrMap.PARTSTAT_DECLINED;
if (needReplyEmail) {
String reason = L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonRecurring, lc);
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_DECLINE);
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, false, reason + "\r\n", this, invite, new Invite[] { replyInv }, mmInv);
replySent = true;
}
} else if (resource.autoDeclineIfBusy()) {
// Auto decline is enabled. Let's check for conflicts.
int maxNumConflicts = resource.getMaxNumConflictsAllowed();
int maxPctConflicts = resource.getMaxPercentConflictsAllowed();
ConflictCheckResult checkResult = checkAvailability(opTime, invite, maxNumConflicts, maxPctConflicts);
if (checkResult != null) {
List<Conflict> conflicts = checkResult.getConflicts();
if (conflicts.size() > 0) {
if (invite.isRecurrence() && !checkResult.tooManyConflicts()) {
// There are some conflicts, but within resource's allowed limit.
if (resource.autoAcceptDecline()) {
// Let's accept partially. (Accept the series and decline conflicting instances.)
List<Invite> replyInvites = new ArrayList<Invite>();
// the REPLY for the ACCEPT of recurrence series
Invite acceptInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_ACCEPT);
for (Conflict conflict : conflicts) {
Instance inst = conflict.getInstance();
InviteInfo invInfo = inst.getInviteInfo();
Invite inv = getInvite(invInfo.getMsgId(), invInfo.getComponentId());
RecurId rid = inst.makeRecurId(inv);
// Record the decline status in reply list.
getReplyList().modifyPartStat(resource, rid, null, resource.getName(), null, null, IcalXmlStrMap.PARTSTAT_DECLINED, false, invite.getSeqNo(), opTime);
replyListUpdated = true;
// Make REPLY VEVENT for the declined instance.
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, inv, rid, CalendarMailSender.VERB_DECLINE);
replyInvites.add(replyInv);
}
if (needReplyEmail) {
ICalTimeZone tz = chooseReplyTZ(invite);
// Send one email to accept the series.
String declinedInstances = getDeclinedTimesString(octxt, mbox, conflicts, invite.isAllDayEvent(), tz, lc);
String msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclinedInstances, lc) + "\r\n\r\n" + declinedInstances;
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_ACCEPT, true, msg, this, invite, new Invite[] { acceptInv }, mmInv);
// Send another email to decline instances, all in one email.
String conflictingTimes = getBusyTimesString(octxt, mbox, conflicts, tz, lc, false);
msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclinedInstances, lc) + "\r\n\r\n" + declinedInstances + "\r\n" + L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonConflict, lc) + "\r\n\r\n" + conflictingTimes;
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, true, msg, this, invite, replyInvites.toArray(new Invite[0]), mmInv);
replySent = true;
}
} else {
// Auto-accept is not enabled. Auto-decline is enabled, but there weren't
// enough conflicting instances to decline outright. So we just stay
// silent and let the human admin deal with it. This case is rather
// ambiguous, and can be avoided by configuring the resource to allow
// zero conflicting instance.
}
} else {
// Too many conflicts. Decline outright.
partStat = IcalXmlStrMap.PARTSTAT_DECLINED;
if (needReplyEmail) {
ICalTimeZone tz = chooseReplyTZ(invite);
String msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonConflict, lc) + "\r\n\r\n" + getBusyTimesString(octxt, mbox, conflicts, tz, lc, checkResult.hasMoreConflicts());
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_DECLINE);
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, false, msg, this, invite, new Invite[] { replyInv }, mmInv);
replySent = true;
}
}
}
}
}
if (!replySent && IcalXmlStrMap.PARTSTAT_ACCEPTED.equals(partStat)) {
if (needReplyEmail) {
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_ACCEPT);
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_ACCEPT, false, null, this, invite, new Invite[] { replyInv }, mmInv);
}
}
// Record the final outcome in the replies list.
if (IcalXmlStrMap.PARTSTAT_NEEDS_ACTION.equals(partStat)) {
getReplyList().modifyPartStat(resource, invite.getRecurId(), null, resource.getName(), null, null, partStat, false, invite.getSeqNo(), opTime);
replyListUpdated = true;
}
if (forCreate && replyListUpdated)
saveMetadata();
}
}
CreateCalendarItemRecorder recorder = (CreateCalendarItemRecorder) mbox.getRedoRecorder();
recorder.setCalendarItemPartStat(partStat);
invite.updateMyPartStat(account, partStat);
if (forCreate) {
Invite defaultInvite = getDefaultInviteOrNull();
if (defaultInvite != null && !defaultInvite.equals(invite) && !partStat.equals(defaultInvite.getPartStat())) {
defaultInvite.updateMyPartStat(account, partStat);
saveMetadata();
}
}
return partStat;
}
use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.
the class CalendarItem method modifyPartStat.
/**
*
* Used when we're sending out a reply -- we add a "reply" record to this appointment/task...this
* ends up affecting our "Effective PartStat" (ie if we ACCEPT a meeting, then our effective partstat
* changes)
*
* @param acctOrNull
* @param recurId
* @param cnStr
* @param addressStr
* @param cutypeStr
* @param roleStr
* @param partStatStr
* @param rsvp
* @param seqNo
* @param dtStamp
* @throws ServiceException
*/
void modifyPartStat(Account acctOrNull, RecurId recurId, String cnStr, String addressStr, String cutypeStr, String roleStr, String partStatStr, Boolean needsReply, int seqNo, long dtStamp) throws ServiceException {
mReplyList.modifyPartStat(acctOrNull, recurId, cnStr, addressStr, cutypeStr, roleStr, partStatStr, needsReply, seqNo, dtStamp);
if (addressStr != null) {
Invite inv = getInvite(recurId);
if (inv != null) {
ZAttendee at;
if (acctOrNull != null)
at = inv.getMatchingAttendee(acctOrNull);
else
at = inv.getMatchingAttendee(addressStr);
if (at != null)
at.setPartStat(partStatStr);
}
}
saveMetadata();
}
use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.
the class CalendarItem method getIndexDocuments.
protected List<IndexDocument> getIndexDocuments() throws TemporaryIndexingException {
List<IndexDocument> toRet = new ArrayList<IndexDocument>();
// when this method is called during commit of cancel operation.
if (numInvites() < 1)
return toRet;
Invite defaultInvite = getDefaultInviteOrNull();
String defaultLocation = "";
if (defaultInvite != null && defaultInvite.getLocation() != null)
defaultLocation = defaultInvite.getLocation();
String defaultName = "";
if (defaultInvite != null && defaultInvite.getName() != null)
defaultName = defaultInvite.getName();
String defaultOrganizer = "";
if (defaultInvite != null && defaultInvite.getOrganizer() != null)
defaultOrganizer = defaultInvite.getOrganizer().getIndexString();
for (Invite inv : getInvites()) {
StringBuilder s = new StringBuilder();
List<String> toAddrs = new ArrayList<String>();
// NAME (subject)
String nameToUse = "";
if (inv.getName() != null) {
s.append(inv.getName()).append(' ');
nameToUse = inv.getName();
} else {
s.append(defaultName).append(' ');
nameToUse = defaultName;
}
// ORGANIZER (from)
String orgToUse = null;
if (inv.getOrganizer() != null) {
String thisInvOrg = inv.getOrganizer().getIndexString();
if (thisInvOrg != null && thisInvOrg.length() > 0)
orgToUse = thisInvOrg;
}
if (orgToUse == null)
orgToUse = defaultOrganizer;
// ATTENDIES (TO)
for (ZAttendee at : inv.getAttendees()) {
try {
toAddrs.add(at.getFriendlyAddress().toString());
s.append(at.getIndexString()).append(' ');
} catch (ServiceException e) {
}
}
s.append(' ');
// LOCATION
if (inv.getLocation() != null) {
s.append(inv.getLocation()).append(' ');
} else {
s.append(defaultLocation).append(' ');
}
// DESCRIPTION
try {
s.append(inv.getDescription()).append(' ');
} catch (ServiceException ex) {
if (ZimbraLog.index.isDebugEnabled()) {
ZimbraLog.index.debug("Caught exception fetching description while indexing CalendarItem " + this.getId() + " skipping", ex);
}
}
// COMMENTS
List<String> comments = inv.getComments();
if (comments != null && !comments.isEmpty()) {
for (String comm : comments) {
s.append(comm).append(' ');
}
}
// CONTACTS
List<String> contacts = inv.getContacts();
if (contacts != null && !contacts.isEmpty()) {
for (String contact : contacts) {
s.append(contact).append(' ');
}
}
// CATEGORIES
List<String> categories = inv.getCategories();
if (categories != null && !categories.isEmpty()) {
for (String cat : categories) {
s.append(cat).append(' ');
}
}
MimeMessage mm = null;
if (!inv.getDontIndexMimeMessage()) {
try {
mm = inv.getMimeMessage();
} catch (ServiceException e) {
if (ZimbraLog.index.isDebugEnabled()) {
ZimbraLog.index.debug("Caught MessagingException for Invite " + inv.toString() + " while fetching MM during indexing of CalendarItem " + this.getId() + " skipping Invite", e);
}
}
}
List<IndexDocument> docList = new ArrayList<IndexDocument>();
if (mm == null) {
// no blob!
IndexDocument doc = new IndexDocument();
// need to properly emulate an indexed Invite message here -- set the TOP partname
doc.addPartName(LuceneFields.L_PARTNAME_TOP);
docList.add(doc);
} else {
try {
ParsedMessage pm = new ParsedMessage(mm, mMailbox.attachmentsIndexingEnabled());
pm.analyzeFully();
if (pm.hasTemporaryAnalysisFailure())
throw new MailItem.TemporaryIndexingException();
docList = pm.getLuceneDocuments();
} catch (ServiceException e) {
if (ZimbraLog.index.isDebugEnabled()) {
ZimbraLog.index.debug("Caught MessagingException for Invite " + inv.toString() + " while indexing CalendarItem " + this.getId() + " skipping Invite", e);
}
}
}
for (IndexDocument doc : docList) {
// update the doc, overriding many of the fields with data from the appointment
doc.addContent(s.toString());
doc.removeTo();
doc.removeFrom();
doc.removeSubject();
for (String to : toAddrs) {
doc.addTo(new RFC822AddressTokenStream(to));
}
doc.addFrom(new RFC822AddressTokenStream(orgToUse));
doc.addSubject(nameToUse);
toRet.add(doc);
}
}
// set the "public"/"private" flag in the index for this appointment
FieldTokenStream fields = new FieldTokenStream(INDEX_FIELD_ITEM_CLASS, isPublic() ? "public" : "private");
for (IndexDocument doc : toRet) {
doc.addField(fields);
}
return toRet;
}
use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.
the class CalItemReminderService method scheduleNextReminders.
/**
* Schedules next reminders for the calendar item.
*
* @param calItem
* @param email
* @param sms
*/
static void scheduleNextReminders(CalendarItem calItem, boolean email, boolean sms) {
try {
CalendarItem.AlarmData alarmData = calItem.getNextEmailAlarm();
if (alarmData == null)
return;
boolean emailAlarmExists = true;
boolean smsAlarmExists = false;
Alarm emailAlarm = alarmData.getAlarm();
List<ZAttendee> recipients = emailAlarm.getAttendees();
if (recipients != null && !recipients.isEmpty()) {
emailAlarmExists = false;
Account acct = calItem.getAccount();
String defaultEmailAddress = acct.getPrefCalendarReminderEmail();
String defaultDeviceAddress = acct.getCalendarReminderDeviceEmail();
for (ZAttendee recipient : recipients) {
if (recipient.getAddress().equals(defaultEmailAddress)) {
emailAlarmExists = true;
}
if (recipient.getAddress().equals(defaultDeviceAddress)) {
smsAlarmExists = true;
}
}
}
if (emailAlarmExists && email) {
scheduleReminder(new CalItemEmailReminderTask(), calItem, alarmData);
}
if (smsAlarmExists && sms) {
scheduleReminder(new CalItemSmsReminderTask(), calItem, alarmData);
}
} catch (ServiceException e) {
ZimbraLog.scheduler.error("Error in scheduling reminder task", e);
}
}
Aggregations