Search in sources :

Example 76 with Invite

use of com.zimbra.cs.mailbox.calendar.Invite 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;
}
Also used : Locale(java.util.Locale) Account(com.zimbra.cs.account.Account) InviteInfo(com.zimbra.cs.mailbox.calendar.InviteInfo) FBInstance(com.zimbra.cs.fb.FreeBusy.FBInstance) ArrayList(java.util.ArrayList) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) CreateCalendarItemPlayer(com.zimbra.cs.redolog.op.CreateCalendarItemPlayer) CreateCalendarItemRecorder(com.zimbra.cs.redolog.op.CreateCalendarItemRecorder) RedoLogProvider(com.zimbra.cs.redolog.RedoLogProvider) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) CalendarResource(com.zimbra.cs.account.CalendarResource) Invite(com.zimbra.cs.mailbox.calendar.Invite) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Example 77 with Invite

use of com.zimbra.cs.mailbox.calendar.Invite 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();
}
Also used : ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 78 with Invite

use of com.zimbra.cs.mailbox.calendar.Invite 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;
}
Also used : IndexDocument(com.zimbra.cs.index.IndexDocument) ParsedMessage(com.zimbra.cs.mime.ParsedMessage) ArrayList(java.util.ArrayList) RFC822AddressTokenStream(com.zimbra.cs.index.analysis.RFC822AddressTokenStream) DbMailItem(com.zimbra.cs.db.DbMailItem) ServiceException(com.zimbra.common.service.ServiceException) MimeMessage(javax.mail.internet.MimeMessage) FixedMimeMessage(com.zimbra.cs.mime.Mime.FixedMimeMessage) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) FieldTokenStream(com.zimbra.cs.index.analysis.FieldTokenStream) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 79 with Invite

use of com.zimbra.cs.mailbox.calendar.Invite in project zm-mailbox by Zimbra.

the class CalendarItem method delete.

@Override
void delete(boolean writeTombstones) throws ServiceException {
    Invite defInv = getDefaultInviteOrNull();
    String sbj;
    if (defInv != null)
        sbj = defInv.isPublic() ? defInv.getName() : "(private)";
    else
        sbj = "(none)";
    ZimbraLog.calendar.info("Deleting CalendarItem: id=%d, folderId=%d, subject=\"%s\", UID=%s", mId, getFolderId(), sbj, mUid);
    if (!isPublic() && !canAccess(ACL.RIGHT_PRIVATE))
        throw ServiceException.PERM_DENIED("you do not have permission to delete private calendar item from the current folder");
    super.delete(writeTombstones);
}
Also used : Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 80 with Invite

use of com.zimbra.cs.mailbox.calendar.Invite in project zm-mailbox by Zimbra.

the class CalendarItem method expandInstances.

/**
     * Expand all the instances for the time period from start to end
     *
     * @param start
     * @param end
     * @param includeAlarmOnlyInstances
     * @return list of Instances for the specified time period
     */
public Collection<Instance> expandInstances(long start, long end, boolean includeAlarmOnlyInstances) throws ServiceException {
    long endAdjusted = end;
    long alarmInstStart = 0;
    if (includeAlarmOnlyInstances) {
        // range.
        if (mAlarmData != null) {
            alarmInstStart = mAlarmData.getNextInstanceStart();
            long nextAlarm = mAlarmData.getNextAtBase();
            if (nextAlarm >= start && nextAlarm < end) {
                if (alarmInstStart >= end)
                    endAdjusted = alarmInstStart + 1;
            }
        }
    }
    List<Instance> instances = new ArrayList<Instance>();
    if (mRecurrence != null) {
        long startTime = System.currentTimeMillis();
        instances = Recurrence.expandInstances(mRecurrence, getId(), start, endAdjusted);
        if (ZimbraLog.calendar.isDebugEnabled()) {
            long elapsed = System.currentTimeMillis() - startTime;
            ZimbraLog.calendar.debug("RECURRENCE EXPANSION for appt/task %s: start=%s, end=%s; took %sms.  %s instances", getId(), start, end, elapsed, instances.size());
        }
    } else {
        // organizer.
        if (mInvites != null) {
            for (Invite inv : mInvites) {
                if (// Skip canceled instances.
                inv.isCancel())
                    continue;
                ParsedDateTime dtStart = inv.getStartTime();
                long invStart = dtStart != null ? dtStart.getUtcTime() : 0;
                ParsedDateTime dtEnd = inv.getEffectiveEndTime();
                long invEnd = dtEnd != null ? dtEnd.getUtcTime() : 0;
                if ((invStart < endAdjusted && invEnd > start) || (dtStart == null)) {
                    Instance inst = new Instance(getId(), new InviteInfo(inv), dtStart != null, dtEnd != null, invStart, invEnd, inv.isAllDayEvent(), dtStart != null ? dtStart.getOffset() : 0, dtEnd != null ? dtEnd.getOffset() : 0, inv.hasRecurId(), false);
                    instances.add(inst);
                }
            }
        }
    }
    // Remove instances that aren't in the actual range.
    for (Iterator<Instance> iter = instances.iterator(); iter.hasNext(); ) {
        Instance inst = iter.next();
        if (inst.hasStart() && inst.hasEnd()) {
            long instStart = inst.getStart();
            long instEnd = inst.getEnd();
            // or instance starts after range end. (i.e. instance does not overlap range)
            if (instStart != alarmInstStart && (instEnd <= start || instStart >= end))
                iter.remove();
        }
    }
    return instances;
}
Also used : InviteInfo(com.zimbra.cs.mailbox.calendar.InviteInfo) ArrayList(java.util.ArrayList) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Aggregations

Invite (com.zimbra.cs.mailbox.calendar.Invite)103 Account (com.zimbra.cs.account.Account)30 Element (com.zimbra.common.soap.Element)26 ServiceException (com.zimbra.common.service.ServiceException)23 CalendarItem (com.zimbra.cs.mailbox.CalendarItem)23 Mailbox (com.zimbra.cs.mailbox.Mailbox)23 MimeMessage (javax.mail.internet.MimeMessage)23 ZVCalendar (com.zimbra.common.calendar.ZCalendar.ZVCalendar)22 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)20 ItemId (com.zimbra.cs.service.util.ItemId)20 ArrayList (java.util.ArrayList)19 IOException (java.io.IOException)18 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)16 TimeZoneMap (com.zimbra.common.calendar.TimeZoneMap)15 ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)15 OperationContext (com.zimbra.cs.mailbox.OperationContext)14 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)14 MessagingException (javax.mail.MessagingException)12 MailServiceException (com.zimbra.cs.mailbox.MailServiceException)11 ParsedMessage (com.zimbra.cs.mime.ParsedMessage)11