protected void setClientAllday(VProperty property) { if (property != null) { // set VALUE=DATE param if (!property.hasParam("VALUE")) { property.addParam("VALUE", "DATE"); } // remove TZID property.removeParam("TZID"); String value = property.getValue(); if (value.length() != 8) { // try to convert datetime value to date value try { Calendar calendar = Calendar.getInstance(); SimpleDateFormat dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); calendar.setTime(dateParser.parse(value)); calendar.add(Calendar.HOUR_OF_DAY, 12); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd"); value = dateFormatter.format(calendar.getTime()); } catch (ParseException e) { LOGGER.warn("Invalid date value in allday event: " + value); } } property.setValue(value); } }
protected String getAttendeeStatus() { String status = null; List<VProperty> attendeeProperties = getFirstVeventProperties("ATTENDEE"); if (attendeeProperties != null) { for (VProperty property : attendeeProperties) { String attendeeEmail = getEmailValue(property); if (email.equalsIgnoreCase(attendeeEmail) && property.hasParam("PARTSTAT")) { // found current user attendee line status = property.getParam("PARTSTAT").getValue(); break; } } } return status; }
protected void fixAlarm(VObject vObject, boolean fromServer) { if (vObject.vObjects != null) { if (Settings.getBooleanProperty("davmail.caldavDisableReminders", false)) { ArrayList<VObject> vAlarms = null; for (VObject vAlarm : vObject.vObjects) { if ("VALARM".equals(vAlarm.type)) { if (vAlarms == null) { vAlarms = new ArrayList<VObject>(); } vAlarms.add(vAlarm); } } // remove all vAlarms if (vAlarms != null) { for (VObject vAlarm : vAlarms) { vObject.vObjects.remove(vAlarm); } } } else { for (VObject vAlarm : vObject.vObjects) { if ("VALARM".equals(vAlarm.type)) { String action = vAlarm.getPropertyValue("ACTION"); if (fromServer && "DISPLAY".equals(action) // convert DISPLAY to AUDIO only if user defined an alarm sound && Settings.getProperty("davmail.caldavAlarmSound") != null) { // Convert alarm to audio for iCal vAlarm.setPropertyValue("ACTION", "AUDIO"); if (vAlarm.getPropertyValue("ATTACH") == null) { // Add defined sound into the audio alarm VProperty vProperty = new VProperty("ATTACH", Settings.getProperty("davmail.caldavAlarmSound")); vProperty.addParam("VALUE", "URI"); vAlarm.addProperty(vProperty); } } else if (!fromServer && "AUDIO".equals(action)) { // Use the alarm action that exchange (and blackberry) understand // (exchange and blackberry don't understand audio actions) vAlarm.setPropertyValue("ACTION", "DISPLAY"); } } } } } }
protected void splitExDate(VObject vObject) { List<VProperty> exDateProperties = vObject.getProperties("EXDATE"); if (exDateProperties != null) { for (VProperty property : exDateProperties) { String value = property.getValue(); if (value.indexOf(',') >= 0) { // split property vObject.removeProperty(property); for (String singleValue : value.split(",")) { VProperty singleProperty = new VProperty("EXDATE", singleValue); singleProperty.setParams(property.getParams()); vObject.addProperty(singleProperty); } } } } }
/** * Get email from property value. * * @param property property * @return email value */ public String getEmailValue(VProperty property) { if (property == null) { return null; } String propertyValue = property.getValue(); if (propertyValue != null && (propertyValue.startsWith("MAILTO:") || propertyValue.startsWith("mailto:"))) { return propertyValue.substring(7); } else { return propertyValue; } }
private void fixTimezone() { if (vTimezone != null && vTimezone.vObjects != null && vTimezone.vObjects.size() > 2) { VObject standard = null; VObject daylight = null; for (VObject vObject : vTimezone.vObjects) { if ("STANDARD".equals(vObject.type)) { if (standard == null || (vObject .getPropertyValue("DTSTART") .compareTo(standard.getPropertyValue("DTSTART")) > 0)) { standard = vObject; } } if ("DAYLIGHT".equals(vObject.type)) { if (daylight == null || (vObject .getPropertyValue("DTSTART") .compareTo(daylight.getPropertyValue("DTSTART")) > 0)) { daylight = vObject; } } } vTimezone.vObjects.clear(); vTimezone.vObjects.add(standard); vTimezone.vObjects.add(daylight); } // fix 3569922: quick workaround for broken Israeli Timezone issue if (vTimezone != null && vTimezone.vObjects != null) { for (VObject vObject : vTimezone.vObjects) { VProperty rrule = vObject.getProperty("RRULE"); if (rrule != null && rrule.getValues().size() == 3 && "BYDAY=-2SU".equals(rrule.getValues().get(1))) { rrule.getValues().set(1, "BYDAY=4SU"); } } } }
protected void setServerAllday(VProperty property) { if (vTimezone != null) { // set TZID param if (!property.hasParam("TZID")) { property.addParam("TZID", vTimezone.getPropertyValue("TZID")); } // remove VALUE property.removeParam("VALUE"); String value = property.getValue(); if (value.length() != 8) { LOGGER.warn("Invalid date value in allday event: " + value); } property.setValue(property.getValue() + "T000000"); } }
/** * Build recipients value for VCalendar. * * @param isNotification if true, filter recipients that should receive meeting notifications * @return notification/event recipients */ public Recipients getRecipients(boolean isNotification) { HashSet<String> attendees = new HashSet<String>(); HashSet<String> optionalAttendees = new HashSet<String>(); // get recipients from first VEVENT List<VProperty> attendeeProperties = getFirstVeventProperties("ATTENDEE"); if (attendeeProperties != null) { for (VProperty property : attendeeProperties) { // exclude current user and invalid values from recipients // also exclude no action attendees String attendeeEmail = getEmailValue(property); if (!email.equalsIgnoreCase(attendeeEmail) && attendeeEmail != null && attendeeEmail.indexOf('@') >= 0 // return all attendees for user calendar folder, filter for notifications && (!isNotification // notify attendee if reply explicitly requested || (property.hasParam("RSVP", "TRUE")) || ( // workaround for iCal bug: do not notify if reply explicitly not requested !(property.hasParam("RSVP", "FALSE")) && ((property.hasParam("PARTSTAT", "NEEDS-ACTION") // need to include other PARTSTATs participants for CANCEL notifications || property.hasParam("PARTSTAT", "ACCEPTED") || property.hasParam("PARTSTAT", "DECLINED") || property.hasParam("PARTSTAT", "TENTATIVE")))))) { if (property.hasParam("ROLE", "OPT-PARTICIPANT")) { optionalAttendees.add(attendeeEmail); } else { attendees.add(attendeeEmail); } } } } Recipients recipients = new Recipients(); recipients.organizer = getEmailValue(getFirstVeventProperty("ORGANIZER")); recipients.attendees = StringUtil.join(attendees, ", "); recipients.optionalAttendees = StringUtil.join(optionalAttendees, ", "); return recipients; }
private void fixAttendees(VObject vObject, boolean fromServer) { if (vObject.properties != null) { for (VProperty property : vObject.properties) { if ("ATTENDEE".equalsIgnoreCase(property.getKey())) { if (fromServer) { // If this is coming from the server, strip out RSVP for this // user as an attendee where the partstat is something other // than PARTSTAT=NEEDS-ACTION since the RSVP confuses iCal4 into // thinking the attendee has not replied if (isCurrentUser(property) && property.hasParam("RSVP", "TRUE")) { VProperty.Param partstat = property.getParam("PARTSTAT"); if (partstat == null || !"NEEDS-ACTION".equals(partstat.getValue())) { property.removeParam("RSVP"); } } } else { property.setValue(replaceIcal4Principal(property.getValue())); } } } } }
private boolean isCurrentUser(VProperty property) { return property.getValue().equalsIgnoreCase("mailto:" + email); }
private void fixTzid(VProperty property) { if (property != null && !property.hasParam("TZID")) { property.addParam("TZID", vTimezone.getPropertyValue("TZID")); } }
protected void fixVCalendar(boolean fromServer) { // set iCal 4 global X-CALENDARSERVER-ACCESS from CLASS if (fromServer) { setPropertyValue("X-CALENDARSERVER-ACCESS", getCalendarServerAccess()); } // iCal 4 global X-CALENDARSERVER-ACCESS String calendarServerAccess = getPropertyValue("X-CALENDARSERVER-ACCESS"); String now = ExchangeSession.getZuluDateFormat().format(new Date()); // fix method from iPhone if (!fromServer && getPropertyValue("METHOD") == null) { setPropertyValue("METHOD", "PUBLISH"); } // rename TZID for maximum iCal/iPhone compatibility String tzid = null; if (fromServer) { // get current tzid VObject vObject = vTimezone; if (vObject != null) { String currentTzid = vObject.getPropertyValue("TZID"); // fix TZID with \n (Exchange 2010 bug) if (currentTzid != null && currentTzid.endsWith("\n")) { currentTzid = currentTzid.substring(0, currentTzid.length() - 1); vObject.setPropertyValue("TZID", currentTzid); } if (currentTzid != null && currentTzid.indexOf(' ') >= 0) { try { tzid = ResourceBundle.getBundle("timezones").getString(currentTzid); vObject.setPropertyValue("TZID", tzid); } catch (MissingResourceException e) { LOGGER.debug("Timezone " + currentTzid + " not found in rename table"); } } } } if (!fromServer) { fixTimezone(); } // iterate over vObjects for (VObject vObject : vObjects) { if ("VEVENT".equals(vObject.type)) { if (calendarServerAccess != null) { vObject.setPropertyValue("CLASS", getEventClass(calendarServerAccess)); // iCal 3, get X-CALENDARSERVER-ACCESS from local VEVENT } else if (vObject.getPropertyValue("X-CALENDARSERVER-ACCESS") != null) { vObject.setPropertyValue( "CLASS", getEventClass(vObject.getPropertyValue("X-CALENDARSERVER-ACCESS"))); } if (fromServer) { // remove organizer line for event without attendees for iPhone if (vObject.getProperty("ATTENDEE") == null) { vObject.setPropertyValue("ORGANIZER", null); } // detect allday and update date properties if (isCdoAllDay(vObject)) { setClientAllday(vObject.getProperty("DTSTART")); setClientAllday(vObject.getProperty("DTEND")); setClientAllday(vObject.getProperty("RECURRENCE-ID")); } String cdoBusyStatus = vObject.getPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS"); if (cdoBusyStatus != null) { vObject.setPropertyValue( "TRANSP", !"FREE".equals(cdoBusyStatus) ? "OPAQUE" : "TRANSPARENT"); } // Apple iCal doesn't understand this key, and it's entourage // specific (i.e. not needed by any caldav client): strip it out vObject.removeProperty("X-ENTOURAGE_UUID"); splitExDate(vObject); // remove empty properties if ("".equals(vObject.getPropertyValue("LOCATION"))) { vObject.removeProperty("LOCATION"); } if ("".equals(vObject.getPropertyValue("DESCRIPTION"))) { vObject.removeProperty("DESCRIPTION"); } if ("".equals(vObject.getPropertyValue("CLASS"))) { vObject.removeProperty("CLASS"); } // rename TZID if (tzid != null) { VProperty dtStart = vObject.getProperty("DTSTART"); if (dtStart != null && dtStart.getParam("TZID") != null) { dtStart.setParam("TZID", tzid); } VProperty dtEnd = vObject.getProperty("DTEND"); if (dtEnd != null && dtStart.getParam("TZID") != null) { dtEnd.setParam("TZID", tzid); } VProperty reccurrenceId = vObject.getProperty("RECURRENCE-ID"); if (reccurrenceId != null && reccurrenceId.getParam("TZID") != null) { reccurrenceId.setParam("TZID", tzid); } } // remove unsupported attachment reference if (vObject.getProperty("ATTACH") != null) { List<String> toRemoveValues = null; List<String> values = vObject.getProperty("ATTACH").getValues(); for (String value : values) { if (value.indexOf("CID:") >= 0) { if (toRemoveValues == null) { toRemoveValues = new ArrayList<String>(); } toRemoveValues.add(value); } } if (toRemoveValues != null) { values.removeAll(toRemoveValues); if (values.size() == 0) { vObject.removeProperty("ATTACH"); } } } } else { // add organizer line to all events created in Exchange for active sync String organizer = getEmailValue(vObject.getProperty("ORGANIZER")); if (organizer == null) { vObject.setPropertyValue("ORGANIZER", "MAILTO:" + email); } else if (!email.equalsIgnoreCase(organizer) && vObject.getProperty("X-MICROSOFT-CDO-REPLYTIME") == null) { vObject.setPropertyValue("X-MICROSOFT-CDO-REPLYTIME", now); } // set OWA allday flag vObject.setPropertyValue( "X-MICROSOFT-CDO-ALLDAYEVENT", isAllDay(vObject) ? "TRUE" : "FALSE"); vObject.setPropertyValue( "X-MICROSOFT-CDO-BUSYSTATUS", !"TRANSPARENT".equals(vObject.getPropertyValue("TRANSP")) ? "BUSY" : "FREE"); if (isAllDay(vObject)) { // convert date values to outlook compatible values setServerAllday(vObject.getProperty("DTSTART")); setServerAllday(vObject.getProperty("DTEND")); } else { fixTzid(vObject.getProperty("DTSTART")); fixTzid(vObject.getProperty("DTEND")); } } fixAttendees(vObject, fromServer); fixAlarm(vObject, fromServer); } } }
protected boolean isAllDay(VObject vObject) { VProperty dtstart = vObject.getProperty("DTSTART"); return dtstart != null && dtstart.hasParam("VALUE", "DATE"); }