/** * See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Board of Regents of the University of Wisconsin System * licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.microsoft.exchange.impl; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.xml.datatype.XMLGregorianCalendar; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.DateTime; import net.fortuna.ical4j.model.Dur; import net.fortuna.ical4j.model.Parameter; import net.fortuna.ical4j.model.ParameterList; import net.fortuna.ical4j.model.TextList; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.component.VTimeZone; import net.fortuna.ical4j.model.component.VToDo; import net.fortuna.ical4j.model.parameter.Cn; import net.fortuna.ical4j.model.parameter.CuType; import net.fortuna.ical4j.model.parameter.PartStat; import net.fortuna.ical4j.model.parameter.Role; import net.fortuna.ical4j.model.property.Attendee; import net.fortuna.ical4j.model.property.Categories; import net.fortuna.ical4j.model.property.Clazz; import net.fortuna.ical4j.model.property.DtEnd; import net.fortuna.ical4j.model.property.DtStamp; import net.fortuna.ical4j.model.property.DtStart; import net.fortuna.ical4j.model.property.Duration; import net.fortuna.ical4j.model.property.Location; import net.fortuna.ical4j.model.property.Organizer; import net.fortuna.ical4j.model.property.Priority; import net.fortuna.ical4j.model.property.Status; import net.fortuna.ical4j.model.property.Summary; import net.fortuna.ical4j.model.property.Transp; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.XProperty; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.CollectionUtils; import com.microsoft.exchange.ExchangeEventConverter; import com.microsoft.exchange.exception.ExchangeEventConverterException; import com.microsoft.exchange.ical.model.EmailAddressMailboxType; import com.microsoft.exchange.ical.model.EmailAddressRoutingType; import com.microsoft.exchange.ical.model.ExchangeEndTimeZoneProperty; import com.microsoft.exchange.ical.model.ExchangeStartTimeZoneProperty; import com.microsoft.exchange.ical.model.ExchangeTimeZoneProperty; import com.microsoft.exchange.ical.model.ItemTypeChangeKey; import com.microsoft.exchange.ical.model.ItemTypeItemId; import com.microsoft.exchange.ical.model.ItemTypeParentFolderChangeKey; import com.microsoft.exchange.ical.model.ItemTypeParentFolderId; import com.microsoft.exchange.ical.model.PathToExtendedFieldTypePropertyId; import com.microsoft.exchange.ical.model.PathToExtendedFieldTypePropertySetId; import com.microsoft.exchange.ical.model.PathToExtendedFieldTypePropertyTag; import com.microsoft.exchange.ical.model.PathToExtendedFieldTypePropertyType; import com.microsoft.exchange.types.ArrayOfStringsType; import com.microsoft.exchange.types.AttendeeType; import com.microsoft.exchange.types.CalendarItemType; import com.microsoft.exchange.types.CalendarItemTypeType; import com.microsoft.exchange.types.DistinguishedPropertySetType; import com.microsoft.exchange.types.EmailAddressType; import com.microsoft.exchange.types.ExtendedPropertyType; import com.microsoft.exchange.types.FolderIdType; import com.microsoft.exchange.types.ImportanceChoicesType; import com.microsoft.exchange.types.ItemIdType; import com.microsoft.exchange.types.ItemType; import com.microsoft.exchange.types.LegacyFreeBusyType; import com.microsoft.exchange.types.MailboxTypeType; import com.microsoft.exchange.types.MapiPropertyTypeType; import com.microsoft.exchange.types.NonEmptyArrayOfAttendeesType; import com.microsoft.exchange.types.NonEmptyArrayOfPropertyValuesType; import com.microsoft.exchange.types.PathToExtendedFieldType; import com.microsoft.exchange.types.ResponseTypeType; import com.microsoft.exchange.types.SensitivityChoicesType; import com.microsoft.exchange.types.SingleRecipientType; import com.microsoft.exchange.types.TaskType; import com.microsoft.exchange.types.TimeZoneDefinitionType; public class ExchangeEventConverterImpl implements ExchangeEventConverter { protected Log log = LogFactory.getLog(this.getClass()); @Override public Calendar convertToCalendar(Collection<ItemType> items, String upn) { Calendar result = new Calendar(); result.getProperties().add(PROD_ID); result.getProperties().add(VERSION); int size = CollectionUtils.isEmpty(items) ? 0 : items.size(); log.debug("attempting to convert "+size+" items"); if(!CollectionUtils.isEmpty(items)){ for(ItemType item: items){ if(item instanceof CalendarItemType) { CalendarItemType calendarItem = (CalendarItemType) item; Pair<VEvent, ArrayList<VTimeZone>> pair = null; try { pair = convertCalendarItemType(calendarItem, upn); } catch (ExchangeEventConverterException e) { log.error("Failed to convert calendarItem:" + e.getMessage()); } if(null != pair){ if(null != pair.getLeft()){ result.getComponents().add(pair.getLeft()); }else{ log.warn("Failed to generate VEvent for CalendarItemType="+calendarItem); } if(!CollectionUtils.isEmpty(pair.getRight())){ log.debug("Generated "+pair.getRight().size()+" VTimeZone components for CalendarItemType="+calendarItem); for(VTimeZone timeZone : pair.getRight()){ result.getComponents().add(timeZone); } }else{ log.warn("Failed to generate VTimeZone for CalendarItemType="+calendarItem); } } }else if(item instanceof TaskType){ TaskType taskItem = (TaskType) item; Pair<VToDo,ArrayList<VTimeZone>> pair = convertTaskType(taskItem, upn); //TODO handle tasks }else{ log.warn("Not an instanceof CalendarItemType | TaskType. Cannot convert item: "+item); } } } return result; } protected Pair<VToDo, ArrayList<VTimeZone>> convertTaskType(TaskType taskItem, String upn) { VToDo task = new VToDo(); ArrayList<VTimeZone> timeZones = new ArrayList<VTimeZone>(); Pair<VToDo, ArrayList<VTimeZone>> pair = Pair.of(task, timeZones); return pair; } /** * * TimeZones. * * @param calendarItem * @param upn * @return * @throws ExchangeEventConverterException */ protected Pair<VEvent, ArrayList<VTimeZone>> convertCalendarItemType(CalendarItemType calendarItem, String upn) throws ExchangeEventConverterException{ VEvent event = new VEvent(); ArrayList<VTimeZone> timeZones = new ArrayList<VTimeZone>(); if(calendarItem.getStart() == null){ throw new ExchangeEventConverterException("calendarItem must have a valid start time."); } if(calendarItem.getEnd() == null && calendarItem.getDuration() == null){ throw new ExchangeEventConverterException("calendarItem must have a valid end time or duration."); } //does this element have a timezone? XMLGregorianCalendar start = calendarItem.getStart(); DtStart dtStart = new DtStart(new DateTime(start.toGregorianCalendar().getTime())); DtEnd dtEnd = null; if(null != calendarItem.getEnd()){ dtEnd = new DtEnd(new DateTime(calendarItem.getEnd().toGregorianCalendar().getTime())); } //if all day event, must use Date if(null != calendarItem.isIsAllDayEvent() && calendarItem.isIsAllDayEvent()) { dtStart = new DtStart(new Date(start.toGregorianCalendar().getTime()),true); dtEnd = new DtEnd(new Date(calendarItem.getEnd().toGregorianCalendar().getTime()),true); log.debug("set to all day event"); } //this way no vtimezone is needed dtStart.setUtc(true); event.getProperties().add(dtStart); log.debug("added dtStart="+dtStart); if( null != dtEnd ){ dtEnd.setUtc(true); event.getProperties().add(dtEnd); log.debug("added dtEnd="+dtEnd); } //in case dtEnd is not present but duration is. String duration = calendarItem.getDuration(); if(StringUtils.isNotBlank(duration) && event.getProperty(DtEnd.DTEND)==null){ Dur dur = new Dur(duration); Duration durationProperty = new Duration(dur); event.getProperties().add(durationProperty); event.getProperties().remove(DtEnd.DTEND); log.debug("dtend overridden with duration="+durationProperty); } String uid = calendarItem.getUID(); if(StringUtils.isNotBlank(uid)){ Uid uidProperty = new Uid(uid); event.getProperties().add(uidProperty); log.debug("added Uid="+uidProperty); }else{ log.debug("could not generate Uid property."); } //should always set dtstamp, otherwise it's auto-generated and !veventCreatedNow.equals(veventCreatedLater); if(null != calendarItem.getDateTimeCreated()){ DtStamp dtstamp = new DtStamp(new DateTime(calendarItem.getDateTimeCreated().toGregorianCalendar().getTime())); dtstamp.setUtc(true); event.getProperties().remove(event.getProperty(DtStamp.DTSTAMP)); event.getProperties().add(dtstamp); log.debug("overide DtStamp="+dtstamp); }else{ log.debug("could not generate DtStamp, property will be autogenerated."); } String subject = calendarItem.getSubject(); if(StringUtils.isNotBlank(subject)) { Summary summaryProperty = new Summary(subject); event.getProperties().add(summaryProperty); log.debug("add summary="+summaryProperty); }else{ log.debug("could not generate Summary property"); } String location = calendarItem.getLocation(); if(StringUtils.isNotBlank(location)){ event.getProperties().add( new Location( location ) ); }else{ log.debug("could not generate location property"); } LegacyFreeBusyType freeBusy = calendarItem.getLegacyFreeBusyStatus(); Transp transpProperty = Transp.OPAQUE; if(LegacyFreeBusyType.FREE.equals(freeBusy)) { transpProperty = Transp.TRANSPARENT; } event.getProperties().add(transpProperty); log.debug("added Transp="+transpProperty); Status status = Status.VEVENT_CONFIRMED; if(BooleanUtils.isTrue(calendarItem.isIsCancelled())){ status = Status.VEVENT_CANCELLED; } event.getProperties().add(status); log.debug("added Status="+status); boolean organizerIsSet = false; SingleRecipientType calendarItemOrganizer = calendarItem.getOrganizer(); if(null != calendarItemOrganizer ) { Organizer organizer = convertToOrganizer(calendarItemOrganizer); if(null != organizer){ event.getProperties().add(organizer); organizerIsSet = true; log.debug("added Organizer="+organizer); }else{ log.debug("could not gernate Organizer. As a result, attendees will not be added."); } }else{ log.debug("could not gernate Organizer. As a result, attendees will not be added."); } //only add RequiredAttendees, OptionalAttendees and Resources if and only if organizer present. if(organizerIsSet){ ResponseTypeType myResponseType = calendarItem.getMyResponseType(); //add RequiredAttendees NonEmptyArrayOfAttendeesType requiredAttendees = calendarItem.getRequiredAttendees(); if(null != requiredAttendees){ Set<Attendee> attendees = convertRequiredAttendees(requiredAttendees, myResponseType); for(Attendee attendee : attendees){ event.getProperties().add(attendee); } }else{ log.debug("no required attendees."); } //add OptionalAttendees NonEmptyArrayOfAttendeesType optionalAttendees = calendarItem.getOptionalAttendees(); if(null != optionalAttendees){ Set<Attendee> attendees = convertOptionalAttendees(optionalAttendees, myResponseType); for(Attendee attendee : attendees){ event.getProperties().add(attendee); } }else{ log.debug("no optional attendees"); } //add Resources NonEmptyArrayOfAttendeesType resourceAttendees = calendarItem.getResources(); if(null != resourceAttendees){ Set<Attendee> attendees = convertResourceAttendees(resourceAttendees, myResponseType); for(Attendee attendee : attendees){ event.getProperties().add(attendee); } } } CalendarItemTypeType calendarItemType = calendarItem.getCalendarItemType(); if(null != calendarItemType){ if(CalendarItemTypeType.EXCEPTION.equals(calendarItemType) || CalendarItemTypeType.RECURRING_MASTER.equals(calendarItemType)){ log.warn("Recurring Event Detected! This implementation of ExchangeEventConverter does not expand recurrance. You should use a CalendarView to expand recurrence on the Exchagne server. --http://msdn.microsoft.com/en-us/library/office/aa564515(v=exchg.150).aspx"); } } //generate xproperties for standard item properties Collection<XProperty> itemXProperties = generateItemTypeXProperties(calendarItem); for(XProperty xp: itemXProperties){ event.getProperties().add(xp); } //generate XProperty's for ExtendedProperties... List<ExtendedPropertyType> extendedProperties = calendarItem.getExtendedProperties(); if(!CollectionUtils.isEmpty(extendedProperties)){ for(ExtendedPropertyType extendedProperty : extendedProperties){ Collection<XProperty> xProperties = convertExtendedPropertyType(extendedProperty); for(XProperty xp: xProperties){ event.getProperties().add(xp); } } } Pair<VEvent, ArrayList<VTimeZone>> pair = Pair.of(event, timeZones); return pair; } private Collection<XProperty> generateCalendarItemTypeXProperties(CalendarItemType calendarItem){ Collection<XProperty> xprops = new LinkedHashSet<XProperty>(); String timeZone = calendarItem.getTimeZone(); if(StringUtils.isNotBlank(timeZone)){ xprops.add(new ExchangeTimeZoneProperty(timeZone)); }else{ log.warn("unable to generate ExchangeTimeZoneProperty, timeZone is blank"); } TimeZoneDefinitionType startTimeZone = calendarItem.getStartTimeZone(); if(null != startTimeZone && StringUtils.isNotBlank(startTimeZone.getId())){ xprops.add(new ExchangeStartTimeZoneProperty(startTimeZone.getId())); }else{ log.debug("unable to generate ExchangeStartTimeZoneProperty, startTimeZone is blank"); } TimeZoneDefinitionType endTimeZone = calendarItem.getEndTimeZone(); if(null != endTimeZone && StringUtils.isNotBlank(endTimeZone.getId())){ xprops.add(new ExchangeEndTimeZoneProperty(endTimeZone.getId())); }else{ log.debug("unable to generate ExchangeEndTimeZoneProperty, endTimeZone is blank"); } return xprops; } /** * Return a never null but possibly empty {@link Collection} of {@link XProperty} * * Returned {@link XProperty}s may include: * {@link ItemTypeParentFolderId}, * * @param item * @return */ private Collection<XProperty> generateItemTypeXProperties(ItemType item){ Collection<XProperty> xprops = new LinkedHashSet<XProperty>(); FolderIdType parentFolderId = item.getParentFolderId(); if(null != parentFolderId){ String p_id = parentFolderId.getId(); String p_ck = parentFolderId.getChangeKey(); if(StringUtils.isNotBlank(p_id)){ xprops.add(new ItemTypeParentFolderId(parentFolderId)); }else{ log.warn("unable to generate X_EWS_PARENT_FOLDER_ID, parentFolderId is blank"); } if(StringUtils.isNotBlank(p_ck)){ xprops.add(new ItemTypeParentFolderChangeKey(parentFolderId)); }else{ log.warn("unable to generate X_EWS_PARENT_FOLDER_CHANGEKEY, parentFolderChangeKey is blank"); } } ItemIdType itemId = item.getItemId(); if(null != itemId){ String i_id = itemId.getId(); String i_ck = itemId.getChangeKey(); if(StringUtils.isNotBlank(i_id)){ xprops.add(new ItemTypeItemId(itemId)); }else{ log.warn("unable to generate X_EWS_ITEM_ID, itemId is blank"); } if(StringUtils.isNotBlank(i_ck)){ xprops.add(new ItemTypeChangeKey(itemId)); }else{ log.warn("unable to generate X_EWS_ITEM_CHANGEKEY, itemChangeKey is blank"); } } if(item instanceof CalendarItemType){ CalendarItemType calendarItem = (CalendarItemType) item; Collection<XProperty> calendarXProps = generateCalendarItemTypeXProperties(calendarItem); if(!CollectionUtils.isEmpty(calendarXProps)){ xprops.addAll(calendarXProps); } }else { log.warn("item is not a CalendarItemType, X_EWS...TIMEZONE properties will not be generated."); } return xprops; } /** * return a never null but possibly empty {@link Collection} of {@link XProperty} * * if an {@link ExtendedPropertyType} contains multiple values this method will return multiple {@link XProperty}'s. * * @param extendedProperty * @return */ private Collection<XProperty> convertExtendedPropertyType(ExtendedPropertyType extendedProperty){ Collection<XProperty> xprops = new LinkedHashSet<XProperty>(); PathToExtendedFieldType extendedFieldURI = extendedProperty.getExtendedFieldURI(); if(null != extendedFieldURI){ String propertyName = extendedFieldURI.getPropertyName(); if(StringUtils.isBlank(propertyName)){ DistinguishedPropertySetType distinguishedPropertySetId = extendedFieldURI.getDistinguishedPropertySetId(); if(null != distinguishedPropertySetId){ propertyName = distinguishedPropertySetId.value(); } } ParameterList params = new ParameterList(); String exPropSetId = extendedFieldURI.getPropertySetId(); if(StringUtils.isNotBlank(exPropSetId)){ params.add(new PathToExtendedFieldTypePropertySetId(extendedFieldURI)); } Integer exPropId = extendedFieldURI.getPropertyId(); if(StringUtils.isNotBlank(exPropId.toString())){ params.add(new PathToExtendedFieldTypePropertyId(extendedFieldURI)); } MapiPropertyTypeType propertyType = extendedFieldURI.getPropertyType(); if(null != propertyType && StringUtils.isNotBlank(propertyType.value())){ params.add(new PathToExtendedFieldTypePropertyType(extendedFieldURI)); } String propertyTag = extendedFieldURI.getPropertyTag(); if(StringUtils.isNotBlank(propertyTag)){ params.add(new PathToExtendedFieldTypePropertyTag(extendedFieldURI)); } Set<String> xPropertyValues = new HashSet<String>(); if(StringUtils.isNotBlank(propertyName)){ NonEmptyArrayOfPropertyValuesType values = extendedProperty.getValues(); if(null != values && !CollectionUtils.isEmpty(values.getValues())){ xPropertyValues.addAll(values.getValues()); }else if(null != extendedProperty.getValue()){ xPropertyValues.add(extendedProperty.getValue()); } }else{ log.error("Unable to generate XProperty(s). propertyName not found for ExtendedPropertyType="+extendedProperty); } if(!CollectionUtils.isEmpty(xPropertyValues)){ Integer count = 0; for(String xValue: xPropertyValues){ xprops.add(new XProperty(propertyName, params, xValue)); propertyName+="_"+count; count++; } }else{ log.error("Unable to generate XProperty(s). propertyValue(s) not found for ExtendedPropertyType="+extendedProperty); } } return xprops; } /** * Convert the {@link String} argument to a mailto {@link URI} if possible. * * @param emailAddress * @return the email as a URI * @throws IllegalArgumentException * if conversion failed, or if the argument was empty * * <strong>WARNING</strong >A {@link CalendarItemType} may contain attendees that no longer have a valid email address. * If an event contains an attendee that has been deleted, the email address field takes the value of <legacyDn> example: <t:EmailAddress>/O=EXCHANGELABS/OU=EXCHANGE ADMINISTRATIVE GROUP (FYDIBOHF23SPDLT)/CN=RECIPIENTS/CN=F450764bd9384fd3b7a38722504c8815-Documentati</t:EmailAddress> */ public URI emailToURI(final String emailAddress) { Validate.notEmpty(emailAddress, "emailAddress cannot be null"); URI uri = null; try { uri = new URI("mailto:" + emailAddress); } catch (URISyntaxException e) { log.debug("caught URISyntaxException trying to construct mailto URI for " + emailAddress + "\n" + e.getMessage()); } return uri; } /** * This method will return a never null a {@link Pair}<{@link ParameterList},{@link URI}> for a given {@link EmailAddressType} * This method will return a null URI element if the {@link EmailAddressType} does not contain a valid EmailAddress property * This method will return a never null but possibly empty {@link ParameterList}. * * ParamaterList may contain the following {@link Parameter}s: * {@link Cn}, {@link EmailAddressRoutingType}, {@link EmailAddressMailboxType} * * @param recipient * @return */ protected Pair<ParameterList, URI> convertEmailAddressType(EmailAddressType emailAddressType, Role role){ URI uri = null; ParameterList params = new ParameterList(); if(null != emailAddressType){ String emailAddress = emailAddressType.getEmailAddress(); if(StringUtils.isNotBlank(emailAddress)){ uri = emailToURI(emailAddress); }else{ log.warn("convertEmailAddressType: could not generate URI."); } if(null != role){ params.add(role); } String name = emailAddressType.getName(); if(StringUtils.isNotBlank(name)){ params.add(new Cn(name)); }else{ log.debug("convertEmailAddressType: could not generate Cn"); } String routingType = emailAddressType.getRoutingType(); if(StringUtils.isNotBlank(routingType)){ params.add(new EmailAddressRoutingType(routingType)); }else{ log.debug("convertEmailAddressType: could not generate EmailAddressRoutingType"); } MailboxTypeType mailboxType = emailAddressType.getMailboxType(); if(null != mailboxType){ params.add(new EmailAddressMailboxType(mailboxType)); CuType cuType = convertMailboxTypeTypeToCuType(mailboxType,role); params.add(cuType); }else{ log.debug("convertEmailAddressType: could not generate EmailAddressMailboxType"); } }else{ log.debug("convertEmailAddressType: EmailAddressType = null"); } Pair<ParameterList,URI> pair = Pair.of(params, uri); return pair; } /** * This method will attempt to generate a {@link Organizer} from a {@link SingleRecipientType} * * * This method will add {@link PartStat.ACCEPTED} and {@link net.fortuna.ical4j.model.parameter.Role.CHAIR} when an organizer is found. * {@link com.microsoft.exchange.impl.ExchangeEventConverterImpl.convertEmailAddressType(EmailAddressType)} for a list of other paramaters that may be included in the response * * This method will return null if the {@link SingleRecipientType} EmailAddress field is missing or invalid. * * @param calendarItemOrganizer * @return */ public Organizer convertToOrganizer(SingleRecipientType calendarItemOrganizer){ Organizer organizer = null; if(null != calendarItemOrganizer){ Pair<ParameterList, URI> pair = convertEmailAddressType(calendarItemOrganizer.getMailbox(), Role.CHAIR); URI organizerURI = pair.getRight(); ParameterList organizerParams = pair.getLeft(); if(null != organizerURI){ organizer = new Organizer(organizerParams, organizerURI); //organizer is always ACCEPTED organizer.getParameters().add(PartStat.ACCEPTED); }else{ log.debug("convertToOrganizer: organizerURI = null, Organizer = null "); } }else{ log.debug("convertToOrganizer: calendarItemOrganizer = null, Organizer = null "); } return organizer; } /** * This method returns a never null but possibly empty {@link HashSet} of {@link Attendee}s. * This method will attempt to generate a {@link Attendee} for each {@link AttendeeType} contained within @link {@link NonEmptyArrayOfAttendeesType}. * An a {@link Attendee} will not be generated for any {@link AttendeeType} with a missing or invalid {@link EmailAddressType} * * Attendee Responses are only present if you obtained CalendarItem from Exchange as organizer. @see <a href="http://office.microsoft.com/en-us/outlook-help/organize-meetings-with-outlook-RZ001166003.aspx?section=20">Attendees do not see responses</a> * * {@link com.microsoft.exchange.impl.ExchangeEventConverterImpl.convertEmailAddressType(EmailAddressType)} for details on how recipient EmailAddressType properties are mapped to {@link Parameter}s. * * @param attendees * @param myResponseType - a {@link PartStat} parameter will be added to every {@link Attendee} if and only if myResponseType.eqals( {@link ResponseTypeType}.ORGANIZER and the corresponding {@link AttendeeType} contains a valid {@link ResponseTypeType} ) * @param requiredAttendees - Indicates which {@link Role} parameter to add to attendees. True indicates that the {@link NonEmptyArrayOfAttendeesType} represent Role.REQ_PARTICIPANT, false indicates the {@link NonEmptyArrayOfAttendeesType} are optional attendees * @return */ protected Set<Attendee> convertAttendees(NonEmptyArrayOfAttendeesType attendees,ResponseTypeType myResponseType, Role role){ Set<Attendee> attendeeSet = new HashSet<Attendee>(); if(null != attendees && !CollectionUtils.isEmpty(attendees.getAttendees())){ for(AttendeeType attendeeType : attendees.getAttendees()){ if(null != attendeeType){ EmailAddressType mailbox = attendeeType.getMailbox(); Pair<ParameterList, URI> attendeePair = convertEmailAddressType(mailbox, role); URI attendeeURI = attendeePair.getRight(); ParameterList attendeeParams = attendeePair.getLeft(); if(null != attendeeURI){ Attendee attendee = new Attendee(attendeeParams, attendeeURI); if(null != myResponseType && myResponseType.equals(ResponseTypeType.ORGANIZER)){ //responseType should be present ResponseTypeType responseType = attendeeType.getResponseType(); if(null != responseType){ //go ahead and add a partstat attendee.getParameters().add(convertResponseTypeTypeToPartStat(responseType)); } } if(attendeeSet.add(attendee)){ log.debug("added Attendee="+attendee); } } } } }else{ log.debug("no attendees"); } return attendeeSet; } public Set<Attendee> convertRequiredAttendees(NonEmptyArrayOfAttendeesType attendees,ResponseTypeType myResponseType){ return convertAttendees(attendees, myResponseType, Role.REQ_PARTICIPANT); } public Set<Attendee> convertOptionalAttendees(NonEmptyArrayOfAttendeesType attendees,ResponseTypeType myResponseType){ return convertAttendees(attendees, myResponseType, Role.OPT_PARTICIPANT); } public Set<Attendee> convertResourceAttendees(NonEmptyArrayOfAttendeesType attendees,ResponseTypeType myResponseType){ return convertAttendees(attendees, myResponseType, Role.NON_PARTICIPANT); } /** * Returns a never null {@link PartStat} for a given {@link ResponseTypeType} * * ResponseTypeType.ORGANIZER => PartStat.ACCEPTED * ResponseTypeType.ACCEPT => PartStat.ACCEPTED * ResponseTypeType.DECLINE => PartStat.DECLINED * ResponseTypeType.TENTATIVE => PartStat.TENTATIVE * All other ResponseTypeTypes => PartStat.NEEDS_ACTION * * @param responseType * @return */ public static PartStat convertResponseTypeTypeToPartStat(ResponseTypeType responseType) { if(null != responseType) { if(responseType.equals(ResponseTypeType.ACCEPT) || responseType.equals(ResponseTypeType.ORGANIZER)) { return PartStat.ACCEPTED; }else if (responseType.equals(ResponseTypeType.DECLINE)) { return PartStat.DECLINED; }else if (responseType.equals(ResponseTypeType.TENTATIVE)) { return PartStat.TENTATIVE; } } return PartStat.NEEDS_ACTION; } /** * Returns a never null {@link ResponseTypeType} for a given {@link PartStat} * * PartStat.ACCEPTED => ResponseTypeType.ACCEPT * PartStat.DECLINED => ResponseTypeType.DECLINE * PartStat.TENTATIVE => ResponseTypeType.TENTATIVE * PartStat.NEEDS_ACTION => ResponseTypeType.NO_RESPONSE_RECEIVED * All other PartStats => ResponseTypeType.UNKNOWN * * @param partStat * @return */ public static ResponseTypeType convertPartStatToResponseTypeType(PartStat partStat){ if(null != partStat){ if(partStat.equals(PartStat.ACCEPTED)){ return ResponseTypeType.ACCEPT; }else if(partStat.equals(PartStat.DECLINED)){ return ResponseTypeType.DECLINE; }else if(partStat.equals(PartStat.TENTATIVE)){ return ResponseTypeType.TENTATIVE; }else if(partStat.equals(PartStat.NEEDS_ACTION)){ return ResponseTypeType.NO_RESPONSE_RECEIVED; } } return ResponseTypeType.UNKNOWN; } /** * Return a never null {@link Clazz} for a given {@link SensitivityChoicesType} * * @see <a href="http://windowsitpro.com/outlook/outlook-using-sensitivity-levels-appointments">Using Sensitivity Levels with Appointments</a> * * SensitivityChoicesType.CONFIDENTIAL => Clazz.CONFIDENTIAL * SensitivityChoicesType.NORMAL => Clazz.PUBLIC * All other SensitivityChoicesType => Clazz.PRIVATE * * @param sensitivity * @return */ public static Clazz convertSensitivityToClazz(SensitivityChoicesType sensitivity){ Clazz clazz = Clazz.PRIVATE; if(null != sensitivity){ if(sensitivity.equals(SensitivityChoicesType.CONFIDENTIAL)){ clazz = Clazz.CONFIDENTIAL; }else if(sensitivity.equals(SensitivityChoicesType.NORMAL)){ clazz = Clazz.PUBLIC; } } return clazz; } /** * Return a never null {@link SensitivityChoicesType} for a given {@link Clazz} * * Clazz.CONFIDENTIAL => SensitivityChoicesType.CONFIDENTIAL * Clazz.PUBLIC => SensitivityChoicesType.NORMAL * All other Clazz => SensitivityChoicesType.PRIVATE * * @param clazz * @return */ public static SensitivityChoicesType convertClazzToSensitivityChoicesType(Clazz clazz){ SensitivityChoicesType sensitivity = SensitivityChoicesType.PRIVATE; if(null != clazz){ if(clazz.equals(Clazz.CONFIDENTIAL)){ sensitivity = SensitivityChoicesType.CONFIDENTIAL; }else if(clazz.equals(Clazz.PUBLIC)){ sensitivity = SensitivityChoicesType.NORMAL; } } return sensitivity; } /** * Returns a never null {@link Priority} for a given {@link ImportanceChoicesType} * * Defaults to Priority.MEDIUM; * * @param importance * @return */ public static Priority convertImportanceChoicesTypeToPriority(ImportanceChoicesType importance){ Priority priority = Priority.MEDIUM; if(null != importance){ if(importance.equals(ImportanceChoicesType.HIGH)){ priority = Priority.HIGH; }else if(importance.equals(ImportanceChoicesType.LOW)) priority = Priority.LOW; } return priority; } /** * Returns a never null {@link ImportanceChoicesType} for a given {@link Priority} * * Defaults to ImportanceChoicesType.NORMAL * * Priority (HIGH => 1, NORMAL => 5, LOW => 9) * * @param priority * @return */ public static ImportanceChoicesType convertPriorityToImportanceChoicesType(Priority priority){ ImportanceChoicesType importance = ImportanceChoicesType.NORMAL; if(null != priority ){ if(priority.equals(Priority.HIGH)){ importance = ImportanceChoicesType.HIGH; }else if(priority.equals(Priority.LOW)){ importance = ImportanceChoicesType.LOW; } } return importance; } /** * Return a never null {@link CuType} for a given {@link MailboxTypeType} * * Defaults to CuType.INDIVIDUAL * * @see <a href="http://www.kanzaki.com/docs/ical/cutype.html">Calendar User Type</a> * @see <a href="http://msdn.microsoft.com/en-us/library/office/aa563493(v=exchg.140).aspx">MailboxType</a> * * @param mailboxType * @return */ public static CuType convertMailboxTypeTypeToCuType(MailboxTypeType mailboxType, Role role){ //If not specified on a property that allows this parameter, the default is INDIVIDUAL. CuType cuType = CuType.INDIVIDUAL; if(null != mailboxType){ if(MailboxTypeType.PRIVATE_DL.equals(mailboxType) || MailboxTypeType.PUBLIC_DL.equals(mailboxType)){ cuType = CuType.GROUP; } //TODO this is bad hack if(Role.NON_PARTICIPANT.equals(role)){ cuType = CuType.RESOURCE; } } return cuType; } /** * Returns a never null but possibly empty TextList * * @param strings * @return */ public static TextList convertArrayOfStringsTypeToTextList(ArrayOfStringsType strings){ TextList textList = new TextList(); if(strings != null){ List<String> stringList = strings.getStrings(); if(!CollectionUtils.isEmpty(stringList)){ for(String s : stringList){ textList.add(s); } } } return textList; } /** * Returns a never null {@link Categories} containing one entry for each entry string contained in {@link ArrayOfStringsType} * * @param categories * @return */ public static Categories convertCategories(ArrayOfStringsType arrayOfCategories){ TextList textList = convertArrayOfStringsTypeToTextList(arrayOfCategories); return new Categories(textList); } }