use of com.zimbra.cs.mailbox.Mailbox.SetCalendarItemData in project zm-mailbox by Zimbra.
the class CalendarCollection method createItemFromInvites.
/**
* @param name Preferred DAV basename for the new item - including ".ics" if appropriate.
* @param allowUpdate - PUTs are allowed to update a pre-existing item. POSTs to the containing collection are not.
*/
public DavResource createItemFromInvites(DavContext ctxt, Account account, String name, List<Invite> invites, boolean allowUpdate) throws DavException, IOException {
boolean useEtag = allowUpdate;
try {
String user = account.getName();
/*
* Some of the CalDAV clients do not behave very well when it comes to etags.
* chandler doesn't set User-Agent header, doesn't understand If-None-Match or If-Match headers.
* evolution 2.8 always sets If-None-Match although we return etag in REPORT.
* ical correctly understands etag and sets If-Match for existing etags, but does not use If-None-Match
* for new resource creation.
*/
HttpServletRequest req = ctxt.getRequest();
String etag = null;
if (useEtag) {
etag = req.getHeader(DavProtocol.HEADER_IF_MATCH);
useEtag = (etag != null);
}
String baseName = HttpUtil.urlUnescape(name);
boolean acceptableClientChosenBasename = DebugConfig.enableDAVclientCanChooseResourceBaseName && baseName.equals(name);
if (name.endsWith(CalendarObject.CAL_EXTENSION)) {
name = name.substring(0, name.length() - CalendarObject.CAL_EXTENSION.length());
// Unescape the name (It was encoded in DavContext intentionally)
name = HttpUtil.urlUnescape(name);
}
String uid = findEventUid(invites);
Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account);
CalendarItem origCalItem = null;
// Is the basename of the path client assigned rather than following the standard pattern?
Integer itemId = null;
if (acceptableClientChosenBasename) {
itemId = DavNames.get(this.mMailboxId, this.mId, baseName);
}
if (itemId != null) {
try {
MailItem mailItem = mbox.getItemById(ctxt.getOperationContext(), itemId, MailItem.Type.UNKNOWN);
if (mailItem != null && mailItem instanceof CalendarItem) {
origCalItem = (CalendarItem) mailItem;
}
} catch (ServiceException se) {
}
}
if (origCalItem == null) {
if (uid.equals(name)) {
origCalItem = mbox.getCalendarItemByUid(ctxt.getOperationContext(), name);
} else {
/* the basename of the path doesn't fit our preferred naming convention. */
origCalItem = mbox.getCalendarItemByUid(ctxt.getOperationContext(), uid);
String redirectUrl = null;
if (origCalItem != null) {
if (this.mId != origCalItem.getFolderId()) {
// In another folder, ignore
origCalItem = null;
} else {
// The item exists, but doesn't have this name - UID conflict.
if (acceptableClientChosenBasename) {
redirectUrl = hrefForCalendarItem(origCalItem, user, uid);
} else {
redirectUrl = defaultUrlForCalendarItem(user, uid);
}
throw new DavException.UidConflict("An item with the same UID already exists in the calendar", redirectUrl);
}
}
if ((origCalItem == null) && (!DebugConfig.enableDAVclientCanChooseResourceBaseName)) {
redirectUrl = defaultUrlForCalendarItem(user, uid);
}
if (allowUpdate && (redirectUrl != null)) {
/* SC_FOUND - Status code (302) indicating that the resource reside temporarily under a
* different URI. Since the redirection might be altered on occasion, the client should
* continue to use the Request-URI for future requests.(HTTP/1.1) To represent the status code
* (302), it is recommended to use this variable. Used to be called SC_MOVED_TEMPORARILY
*/
// sets status to SC_FOUND
ctxt.getResponse().sendRedirect(redirectUrl);
StringBuilder wrongUrlMsg = new StringBuilder();
wrongUrlMsg.append("wrong url - redirecting to:\n").append(redirectUrl);
throw new DavException(wrongUrlMsg.toString(), HttpServletResponse.SC_FOUND, null);
}
}
}
if (origCalItem == null && useEtag) {
throw new DavException("event not found", HttpServletResponse.SC_NOT_FOUND, null);
}
if (origCalItem != null && !allowUpdate) {
throw new DavException.UidConflict("An item with the same UID already exists in the calendar", hrefForCalendarItem(origCalItem, user, uid));
}
boolean isNewItem = true;
if (useEtag) {
String itemEtag = MailItemResource.getEtag(origCalItem);
if (!itemEtag.equals(etag)) {
throw new DavException(String.format("CalDAV client has stale event: event has different etag (%s) vs %s", itemEtag, etag), HttpServletResponse.SC_PRECONDITION_FAILED);
}
isNewItem = false;
}
// prepare to call Mailbox.setCalendarItem()
int flags = 0;
String[] tags = null;
List<ReplyInfo> replies = null;
Invite[] origInvites = null;
if (origCalItem != null) {
flags = origCalItem.getFlagBitmask();
tags = origCalItem.getTags();
replies = origCalItem.getAllReplies();
origInvites = origCalItem.getInvites();
}
SetCalendarItemData scidDefault = new SetCalendarItemData();
SetCalendarItemData[] scidExceptions = null;
int idxExceptions = 0;
boolean first = true;
for (Invite i : invites) {
// check for valid uid.
if (i.getUid() == null)
i.setUid(uid);
adjustOrganizer(ctxt, i);
// Carry over the MimeMessage/ParsedMessage to preserve any attachments.
// CalDAV clients don't support attachments, and on edit we have to either
// retain existing attachments or drop them. Retaining is better.
ParsedMessage oldPm = null;
if (origCalItem != null) {
Invite oldInv = origCalItem.getInvite(i.getRecurId());
if (oldInv == null && i.hasRecurId()) {
// It's a new exception instance. Inherit from series.
oldInv = origCalItem.getInvite((RecurId) null);
}
if (oldInv != null) {
MimeMessage mmInv = origCalItem.getSubpartMessage(oldInv.getMailItemId());
oldPm = mmInv != null ? new ParsedMessage(mmInv, false) : null;
}
}
if (first) {
scidDefault.invite = i;
scidDefault.message = oldPm;
first = false;
} else {
SetCalendarItemData scid = new SetCalendarItemData();
scid.invite = i;
scid.message = oldPm;
if (scidExceptions == null) {
scidExceptions = new SetCalendarItemData[invites.size() - 1];
}
scidExceptions[idxExceptions++] = scid;
}
// For attendee case, update replies list with matching ATTENDEE from the invite.
if (!i.isOrganizer() && replies != null) {
ZAttendee at = i.getMatchingAttendee(account);
if (at != null) {
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(account);
ReplyInfo newReply = null;
for (Iterator<ReplyInfo> replyIter = replies.iterator(); replyIter.hasNext(); ) {
ReplyInfo reply = replyIter.next();
if (acctMatcher.matches(reply.getAttendee().getAddress())) {
RecurId ridR = reply.getRecurId(), ridI = i.getRecurId();
if ((ridR == null && ridI == null) || (ridR != null && ridR.equals(ridI))) {
// matching RECURRENCE-ID
// No need to compare SEQUENCE and DTSTAMP of existing reply and new invite.
// We're just going to take what the caldav client sent, even if it's older
// than the existing reply.
replyIter.remove();
if (!IcalXmlStrMap.PARTSTAT_NEEDS_ACTION.equalsIgnoreCase(at.getPartStat())) {
newReply = new ReplyInfo(at, i.getSeqNo(), i.getDTStamp(), ridI);
}
break;
}
}
}
if (newReply != null) {
replies.add(newReply);
}
}
}
}
CalendarItem newCalItem = null;
AutoScheduler autoScheduler = AutoScheduler.getAutoScheduler(mbox, this.getCalendarMailbox(ctxt), origInvites, mId, flags, tags, scidDefault, scidExceptions, replies, ctxt);
if (autoScheduler == null) {
newCalItem = mbox.setCalendarItem(ctxt.getOperationContext(), mId, flags, tags, scidDefault, scidExceptions, replies, CalendarItem.NEXT_ALARM_KEEP_CURRENT);
} else {
// Note: This also sets the calendar item
newCalItem = autoScheduler.doSchedulingActions();
}
if (newCalItem == null) {
throw new DavException("cannot create icalendar item - corrupt ICAL?", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
if (!uid.equals(name)) {
if (acceptableClientChosenBasename) {
DavNames.put(DavNames.DavName.create(this.mMailboxId, newCalItem.getFolderId(), baseName), newCalItem.getId());
}
}
return new CalendarObject.LocalCalendarObject(ctxt, newCalItem, isNewItem);
} catch (BadOrganizerException.DiffOrganizerInComponentsException e) {
throw new DavException.NeedSameOrganizerInAllComponents(e.getMessage());
} catch (BadOrganizerException e) {
// FORBIDDEN if we aren't going to be able to cope with the data
throw new DavException(e.getMessage(), HttpServletResponse.SC_FORBIDDEN, e);
} catch (ServiceException e) {
if (e.getCode().equals(ServiceException.FORBIDDEN)) {
throw new DavException(e.getMessage(), HttpServletResponse.SC_FORBIDDEN, e);
} else {
throw new DavException("cannot create icalendar item", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
}
}
}
use of com.zimbra.cs.mailbox.Mailbox.SetCalendarItemData in project zm-mailbox by Zimbra.
the class AddCalendarItemInvite method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Account acct = getRequestedAccount(zsc);
Mailbox mbox = getRequestedMailbox(zsc);
OperationContext octxt = getOperationContext(zsc, context);
AddInviteParser parser = new AddInviteParser();
SetCalendarItemData scid = SetCalendarItem.getSetCalendarItemData(zsc, octxt, acct, mbox, request, parser);
Invite inv = scid.invite;
CalendarItem calItem = mbox.getCalendarItemByUid(octxt, inv.getUid());
int folderId = inv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
if (calItem != null) {
int f = calItem.getFolderId();
if (f != Mailbox.ID_FOLDER_TRASH && f != Mailbox.ID_FOLDER_SPAM)
folderId = f;
}
// it's the correct organizer.
if (!inv.hasOrganizer() && inv.hasOtherAttendees()) {
if (scid.message == null) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
inv.clearAttendees();
} else {
String fromEmail = scid.message.getSenderEmail(true);
if (fromEmail != null) {
boolean dangerousSender = false;
// Is sender == recipient? If so, clear attendees.
String intendedForAddress;
try {
intendedForAddress = scid.message.getMimeMessage().getHeader(CalendarMailSender.X_ZIMBRA_CALENDAR_INTENDED_FOR, null);
} catch (MessagingException e) {
throw ServiceException.FAILURE("error parsing message", e);
}
if (intendedForAddress != null && intendedForAddress.length() > 0) {
if (intendedForAddress.equalsIgnoreCase(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
inv.clearAttendees();
dangerousSender = true;
}
} else if (AccountUtil.addressMatchesAccount(acct, fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
inv.clearAttendees();
dangerousSender = true;
}
if (!dangerousSender) {
ZOrganizer org = new ZOrganizer(fromEmail, null);
String senderEmail = scid.message.getSenderEmail(false);
if (senderEmail != null && !senderEmail.equalsIgnoreCase(fromEmail))
org.setSentBy(senderEmail);
inv.setOrganizer(org);
ZimbraLog.calendar.info("Got malformed invite that lists attendees without specifying an organizer. " + "Defaulting organizer to: " + org.toString());
}
}
}
}
// trace logging
String calItemIdStr = calItem != null ? Integer.toString(calItem.getId()) : "(new)";
if (!inv.hasRecurId())
ZimbraLog.calendar.info("<AddCalendarItemInvite> id=%s, folderId=%d, subject=\"%s\", UID=%s", calItemIdStr, folderId, inv.isPublic() ? inv.getName() : "(private)", inv.getUid());
else
ZimbraLog.calendar.info("<AddCalendarItemInvite> id=%s, folderId=%d, subject=\"%s\", UID=%s, recurId=%s", calItemIdStr, folderId, inv.isPublic() ? inv.getName() : "(private)", inv.getUid(), inv.getRecurId().getDtZ());
Element response = getResponseElement(zsc);
if (calItem != null) {
// If the calendar item already has the invite, no need to add again.
RecurId rid = scid.invite.getRecurId();
Invite matchingInv = calItem.getInvite(rid);
if (matchingInv != null && matchingInv.isSameOrNewerVersion(scid.invite)) {
response.addAttribute(MailConstants.A_CAL_ID, calItem.getId());
response.addAttribute(MailConstants.A_CAL_INV_ID, matchingInv.getMailItemId());
response.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, matchingInv.getComponentNum());
return response;
}
}
AddInviteData aid = mbox.addInvite(octxt, inv, folderId, scid.message, false, false, true);
if (aid != null) {
calItem = mbox.getCalendarItemById(octxt, aid.calItemId);
if (calItem != null) {
Invite[] invs = calItem.getInvites(aid.invId);
if (invs != null && invs.length > 0) {
response.addAttribute(MailConstants.A_CAL_ID, aid.calItemId);
response.addAttribute(MailConstants.A_CAL_INV_ID, aid.invId);
response.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, invs[0].getComponentNum());
}
}
}
return response;
}
use of com.zimbra.cs.mailbox.Mailbox.SetCalendarItemData in project zm-mailbox by Zimbra.
the class SetCalendarItem method getSetCalendarItemData.
static SetCalendarItemData getSetCalendarItemData(ZimbraSoapContext zsc, OperationContext octxt, Account acct, Mailbox mbox, Element e, ParseMimeMessage.InviteParser parser) throws ServiceException {
String partStatStr = e.getAttribute(MailConstants.A_CAL_PARTSTAT, IcalXmlStrMap.PARTSTAT_NEEDS_ACTION);
// <M>
Element msgElem = e.getElement(MailConstants.E_MSG);
// check to see whether the entire message has been uploaded under separate cover
String attachmentId = msgElem.getAttribute(MailConstants.A_ATTACHMENT_ID, null);
Element contentElement = msgElem.getOptionalElement(MailConstants.E_CONTENT);
InviteParserResult ipr = null;
MimeMessage mm = null;
if (attachmentId != null) {
ParseMimeMessage.MimeMessageData mimeData = new ParseMimeMessage.MimeMessageData();
mm = SendMsg.parseUploadedMessage(zsc, attachmentId, mimeData);
} else if (contentElement != null) {
mm = ParseMimeMessage.importMsgSoap(msgElem);
} else {
CalSendData dat = handleMsgElement(zsc, octxt, msgElem, acct, mbox, parser);
mm = dat.mMm;
ipr = parser.getResult();
}
if (ipr == null && msgElem.getOptionalElement(MailConstants.E_INVITE) != null) {
ipr = parser.parse(zsc, octxt, mbox.getAccount(), msgElem.getElement(MailConstants.E_INVITE));
// in the <inv> but not in mime parts.
if (ipr != null && ipr.mInvite != null && mm != null) {
String desc = Invite.getDescription(mm, MimeConstants.CT_TEXT_PLAIN);
String descHtml = Invite.getDescription(mm, MimeConstants.CT_TEXT_HTML);
if ((desc != null && desc.length() > 0) || (descHtml != null && descHtml.length() > 0)) {
ipr.mInvite.setDescription(desc, descHtml);
}
}
}
ParsedMessage pm = new ParsedMessage(mm, mbox.attachmentsIndexingEnabled());
Invite inv = (ipr == null ? null : ipr.mInvite);
if (inv == null || inv.getDTStamp() == -1) {
//zdsync if -1 for 4.5 back compat
CalendarPartInfo cpi = pm.getCalendarPartInfo();
ZVCalendar cal = null;
if (cpi != null && CalendarItem.isAcceptableInvite(mbox.getAccount(), cpi)) {
cal = cpi.cal;
}
if (cal == null) {
throw ServiceException.FAILURE("SetCalendarItem could not build an iCalendar object", null);
}
// not applicable in the SetCalendarItem case
boolean sentByMe = false;
Invite iCalInv = Invite.createFromCalendar(acct, pm.getFragment(acct.getLocale()), cal, sentByMe).get(0);
if (inv == null) {
inv = iCalInv;
} else {
//zdsync
inv.setDtStamp(iCalInv.getDTStamp());
//zdsync
inv.setFragment(iCalInv.getFragment());
}
}
inv.setPartStat(partStatStr);
SetCalendarItemData sadata = new SetCalendarItemData();
sadata.invite = inv;
sadata.message = pm;
return sadata;
}
use of com.zimbra.cs.mailbox.Mailbox.SetCalendarItemData in project zm-mailbox by Zimbra.
the class CalDavDataImport method applyRemoteItem.
private MailItem applyRemoteItem(RemoteItem remoteItem, Folder where) throws ServiceException, IOException {
if (!(remoteItem instanceof RemoteCalendarItem)) {
ZimbraLog.datasource.warn("applyRemoteItem: not a calendar item: %s", remoteItem);
return null;
}
RemoteCalendarItem item = (RemoteCalendarItem) remoteItem;
DataSource ds = getDataSource();
DataSourceItem dsItem = DbDataSource.getReverseMapping(ds, item.href);
OperationContext octxt = new OperationContext(mbox);
MailItem mi = null;
boolean isStale = false;
boolean isCreate = false;
if (dsItem.md == null && item.status != Status.deleted) {
dsItem.md = new Metadata();
dsItem.md.put(METADATA_KEY_TYPE, METADATA_TYPE_APPOINTMENT);
}
if (dsItem.itemId == 0) {
isStale = true;
isCreate = true;
} else {
String etag = dsItem.md.get(METADATA_KEY_ETAG, null);
try {
mi = mbox.getItemById(octxt, dsItem.itemId, MailItem.Type.UNKNOWN);
} catch (MailServiceException.NoSuchItemException se) {
ZimbraLog.datasource.warn("applyRemoteItem: calendar item not found: ", remoteItem);
}
if (item.etag == null) {
ZimbraLog.datasource.warn("No Etag returned for item %s", item.href);
isStale = true;
} else if (etag == null) {
ZimbraLog.datasource.warn("Empty etag for item %d", dsItem.itemId);
isStale = true;
} else {
isStale = !item.etag.equals(etag);
}
if (mi == null)
isStale = true;
}
if (item.status == Status.deleted) {
ZimbraLog.datasource.debug("Deleting appointment %s", item.href);
try {
mi = mbox.getItemById(octxt, item.itemId, MailItem.Type.UNKNOWN);
} catch (NoSuchItemException se) {
mi = null;
}
try {
mbox.delete(octxt, item.itemId, MailItem.Type.UNKNOWN);
} catch (ServiceException se) {
ZimbraLog.datasource.warn("Error deleting remotely deleted item %d (%s)", item.itemId, dsItem.remoteId);
}
} else if (isStale) {
ZimbraLog.datasource.debug("Updating stale appointment %s", item.href);
ZCalendar.ZVCalendar vcalendar;
SetCalendarItemData main = new SetCalendarItemData();
SetCalendarItemData[] exceptions = null;
CalDavClient client = null;
try {
client = getClient();
} catch (DavException e) {
throw ServiceException.FAILURE("error creating CalDAV client", e);
}
Appointment appt = client.getCalendarData(new Appointment(item.href, item.etag));
if (appt.data == null) {
ZimbraLog.datasource.warn("No appointment found at " + item.href);
return null;
}
dsItem.md.put(METADATA_KEY_ETAG, appt.etag);
try {
vcalendar = ZCalendar.ZCalendarBuilder.build(appt.data);
List<Invite> invites = Invite.createFromCalendar(mbox.getAccount(), null, vcalendar, true);
if (invites.size() > 1)
exceptions = new SetCalendarItemData[invites.size() - 1];
int pos = 0;
boolean first = true;
for (Invite i : invites) {
if (first) {
main.invite = i;
first = false;
} else {
SetCalendarItemData scid = new SetCalendarItemData();
scid.invite = i;
exceptions[pos++] = scid;
}
}
} catch (Exception e) {
ZimbraLog.datasource.warn("Error parsing appointment ", e);
return null;
}
mi = mbox.setCalendarItem(octxt, where.getId(), 0, null, main, exceptions, null, CalendarItem.NEXT_ALARM_KEEP_CURRENT);
dsItem.itemId = mi.getId();
dsItem.folderId = mi.getFolderId();
if (isCreate) {
DbDataSource.addMapping(ds, dsItem);
} else {
DbDataSource.updateMapping(ds, dsItem);
}
} else {
ZimbraLog.datasource.debug("Appointment up to date %s", item.href);
try {
mi = mbox.getItemById(octxt, dsItem.itemId, MailItem.Type.UNKNOWN);
} catch (NoSuchItemException se) {
// item not found. delete the mapping so it can be downloaded again if needed.
ArrayList<Integer> deletedIds = new ArrayList<Integer>();
deletedIds.add(dsItem.itemId);
DbDataSource.deleteMappings(ds, deletedIds);
}
}
return mi;
}
Aggregations