/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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 org.jasig.schedassist.model;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.UUID;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.DateList;
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.Period;
import net.fortuna.ical4j.model.PeriodList;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.PropertyList;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.PartStat;
import net.fortuna.ical4j.model.parameter.Rsvp;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.Created;
import net.fortuna.ical4j.model.property.Description;
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.LastModified;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.Organizer;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.RDate;
import net.fortuna.ical4j.model.property.RRule;
import net.fortuna.ical4j.model.property.Sequence;
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.Version;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.schedassist.IAffiliationSource;
import org.jasig.schedassist.NullAffiliationSourceImpl;
/**
* Default {@link IEventUtils} implementation.
*
* @author Nicholas Blair
*/
public class DefaultEventUtilsImpl implements IEventUtils {
/**
* Date/time format for iCalendar.
*/
public static final String ICAL_DATETIME_FORMAT = "yyyyMMdd'T'HHmmss'Z'";
/**
* {@link ProdId} attached to {@link Calendar}s sent to the CalDAV server by the Scheduling Assistant.
*/
public static final ProdId PROD_ID = new ProdId("-//jasig.org//Jasig Scheduling Assistant 1.1//EN");
// Commons-Lang provides a thread-safe replacement for SimpleDateFormat
private static final FastDateFormat FASTDATEFORMAT = FastDateFormat.getInstance(ICAL_DATETIME_FORMAT,
TimeZone.getTimeZone("UTC"));
protected final Log LOG = LogFactory.getLog(this.getClass());
private final IAffiliationSource affiliationSource;
private String eventClassForPersonOwners = Clazz.CONFIDENTIAL.getValue();
private String eventClassForResourceOwners = Clazz.PUBLIC.getValue();
/**
* Default constructor, sets the {@link IAffiliationSource} to the
* {@link NullAffiliationSourceImpl} implementation.
*/
public DefaultEventUtilsImpl() {
this(new NullAffiliationSourceImpl());
}
/**
* @param affiliationSource
*/
public DefaultEventUtilsImpl(IAffiliationSource affiliationSource) {
this.affiliationSource = affiliationSource;
}
/**
* @return the affiliationSource
*/
public IAffiliationSource getAffiliationSource() {
return affiliationSource;
}
/**
* @return the eventClassForPersonOwners
*/
public String getEventClassForPersonOwners() {
return eventClassForPersonOwners;
}
/**
* @return the eventClassForResourceOwners
*/
public String getEventClassForResourceOwners() {
return eventClassForResourceOwners;
}
/**
* @param eventClassForPersonOwners the eventClassForPersonOwners to set
*/
public void setEventClassForPersonOwners(String eventClassForPersonOwners) {
this.eventClassForPersonOwners = eventClassForPersonOwners;
}
/**
* @param eventClassForResourceOwners the eventClassForResourceOwners to set
*/
public void setEventClassForResourceOwners(String eventClassForResourceOwners) {
this.eventClassForResourceOwners = eventClassForResourceOwners;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#attendeeMatchesPerson(net.fortuna.ical4j.model.Property, org.jasig.schedassist.model.ICalendarAccount)
*/
@Override
public boolean attendeeMatchesPerson(Property attendee,
ICalendarAccount calendarAccount) {
if(null == attendee) {
return false;
}
Cn cn = (Cn) attendee.getParameter(Cn.CN);
if(null == cn) {
return false;
}
boolean cnResult = cn.getValue().equals(calendarAccount.getDisplayName());
URI mailTo = emailToURI(calendarAccount.getEmailAddress());
boolean mailResult = attendee.getValue().equals(mailTo.toString());
return cnResult && mailResult;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#constructAvailableAppointment(org.jasig.schedassist.model.AvailableBlock, org.jasig.schedassist.model.IScheduleOwner, org.jasig.schedassist.model.IScheduleVisitor, java.lang.String)
*/
@Override
public VEvent constructAvailableAppointment(AvailableBlock block,
IScheduleOwner owner, IScheduleVisitor visitor,
String eventDescription) {
Validate.notNull(block, "available block cannot be null");
Validate.notNull(owner, "schedule owner cannot be null");
Validate.notNull(visitor, "schedule visitor cannot be null");
try {
VEvent event = new VEvent();
event.getProperties().add(new DtStart(new DateTime(convertToICalendarFormat(block.getStartTime()))));
event.getProperties().add(new DtEnd(new DateTime(convertToICalendarFormat(block.getEndTime()))));
Organizer ownerOrganizer = constructOrganizer(owner.getCalendarAccount());
event.getProperties().add(ownerOrganizer);
Attendee visitorAttendee = constructSchedulingAssistantAttendee(visitor.getCalendarAccount(), AppointmentRole.VISITOR);
event.getProperties().add(visitorAttendee);
Attendee ownerAttendee = constructSchedulingAssistantAttendee(owner.getCalendarAccount(), AppointmentRole.OWNER);
event.getProperties().add(ownerAttendee);
// add custom UW-AVAILABLE-APPOINTMENT and UW-AVAILABLE-VERSION
event.getProperties().add(SchedulingAssistantAppointment.TRUE);
event.getProperties().add(AvailableVersion.AVAILABLE_VERSION_1_2);
// add X-Uw-AVAILABLE-VISITORLIMIT
event.getProperties().add(new VisitorLimit(block.getVisitorLimit()));
StringBuilder title = new StringBuilder();
title.append(owner.getPreference(Preferences.MEETING_PREFIX));
// update title with visitor name and add description only if visitorLimit == 1
if(block.getVisitorLimit() == 1) {
title.append(" with ");
title.append(visitor.getCalendarAccount().getDisplayName());
// build event description
Description description = new Description(eventDescription);
event.getProperties().add(description);
}
// finally add meeting title
event.getProperties().add(new Summary(title.toString()));
if(owner.getCalendarAccount() instanceof IDelegateCalendarAccount) {
event.getProperties().add(new Clazz(eventClassForResourceOwners));
} else {
event.getProperties().add(new Clazz(eventClassForPersonOwners));
}
// check if block overrides meeting location
final String blockMeetingLocationOverride = block.getMeetingLocation();
if(StringUtils.isNotBlank(blockMeetingLocationOverride)) {
event.getProperties().add(new Location(blockMeetingLocationOverride));
} else {
// fall back to owner's preferred location (if set)
final String preferredLocation = owner.getPreferredLocation();
if(StringUtils.isNotBlank(preferredLocation)) {
event.getProperties().add(new Location(preferredLocation));
}
}
// add CONFIRMED status
event.getProperties().add(Status.VEVENT_CONFIRMED);
// lastly we must add a UID
event.getProperties().add(generateNewUid());
return event;
} catch (ParseException e) {
throw new IllegalArgumentException("caught ParseException creating event", e);
}
}
/**
* Called by {@link #constructAvailableAppointment(AvailableBlock, IScheduleOwner, IScheduleVisitor, String)};
* returns an appropriate {@link Clazz} depending on whether or not the {@link ICalendarAccount} is a resource account.
*
* @param calendarAccount
* @return an appropriate {@link Clazz} property to attach to the event
*/
protected Clazz determineAppropriateClassProperty(ICalendarAccount calendarAccount) {
if(calendarAccount instanceof IDelegateCalendarAccount) {
return new Clazz(eventClassForResourceOwners);
}
return new Clazz(eventClassForPersonOwners);
}
/**
* Construct an {@link Attendee} property for the specified user and role.
* The PARTSTAT parameter will be set to ACCEPTED.
* The RSVP parameter will be set to FALSE.
* The X-UW-AVAILABLE-APPOINTMENT-ROLE parameter will be set according to the role argument.
* The CN parameter will be set to the {@link ICalendarAccount}'s display name.
* The value will be a mailto address for the {@link ICalendarAccount}'s email address.
*
* @see org.jasig.schedassist.model.IEventUtils#constructSchedulingAssistantAttendee(org.jasig.schedassist.model.ICalendarAccount, org.jasig.schedassist.model.AppointmentRole)
*/
@Override
public Attendee constructSchedulingAssistantAttendee(
ICalendarAccount calendarAccount, AppointmentRole role) {
ParameterList parameterList = new ParameterList();
parameterList.add(PartStat.ACCEPTED);
parameterList.add(Rsvp.FALSE);
parameterList.add(role);
parameterList.add(new Cn(calendarAccount.getDisplayName()));
Attendee attendee = new Attendee(parameterList, emailToURI(calendarAccount.getEmailAddress()));
return attendee;
}
/**
* Construct an {@link Organizer} property for the specified {@link ICalendarAccount}.
*
* @param calendarAccount
* @return an {@link Organizer} property for the {@link ICalendarAccount}
*/
public Organizer constructOrganizer(ICalendarAccount calendarAccount) {
ParameterList parameterList = new ParameterList();
parameterList.add(new Cn(calendarAccount.getDisplayName()));
parameterList.add(AppointmentRole.OWNER);
Organizer organizer = new Organizer(parameterList, emailToURI(calendarAccount.getEmailAddress()));
return organizer;
}
/**
* Walk through the attendee list in the {@link VEvent} argument.
* Return the matching {@link Attendee} for the {@link ICalendarAccount} argument, or null
* if the {@link ICalendarAccount} is not in the attendee list.
*
* @return the matching attendee property, or null
*/
@Override
public Property getAttendeeForUserFromEvent(VEvent event,
ICalendarAccount calendarUser) {
if(null == event || null == calendarUser) {
return null;
}
PropertyList propertyList = getAttendeeListFromEvent(event);
for(Object o: propertyList) {
Property attendee = (Property) o;
if(attendeeMatchesPerson(attendee, calendarUser)) {
return attendee;
}
}
//otherwise the calendarUser might be the organizer
if(isAttendingAsOwner(event, calendarUser)) {
return event.getProperty(Organizer.ORGANIZER);
}
return null;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#getAttendeeListFromEvent(net.fortuna.ical4j.model.component.VEvent)
*/
@Override
public PropertyList getAttendeeListFromEvent(VEvent event) {
if(null == event) {
return new PropertyList();
} else {
PropertyList attendees = event.getProperties(Attendee.ATTENDEE);
return attendees;
}
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#getScheduleVisitorCount(net.fortuna.ical4j.model.component.VEvent)
*/
@Override
public int getScheduleVisitorCount(VEvent event) {
if(null == event) {
return 0;
}
PropertyList propertyList = event.getProperties(Attendee.ATTENDEE);
int count = 0;
for(Object o: propertyList) {
Attendee attendee = (Attendee) o;
Parameter role = attendee.getParameter(AppointmentRole.APPOINTMENT_ROLE);
if(null != role && AppointmentRole.VISITOR.equals(role)) {
count++;
}
}
return count;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#willEventCauseConflict(org.jasig.schedassist.model.ICalendarAccount, net.fortuna.ical4j.model.component.VEvent)
*/
@Override
public boolean willEventCauseConflict(ICalendarAccount calendarAccount, VEvent event) {
// check to see if the owner an attendee and has ACCEPTED
Property ownerAttendee = getAttendeeForUserFromEvent(event, calendarAccount);
if(ownerAttendee != null) {
if(Organizer.ORGANIZER.equals(ownerAttendee.getName())) {
return true;
}
Parameter p = ownerAttendee.getParameter(PartStat.PARTSTAT);
return PartStat.ACCEPTED.equals(p);
}
return false;
}
/**
* Check the event to see if this event represents an existing available appointment
* that the visitor is "visiting" and the owner is "owning" (including the special case
* when visitor and owner are the same person)
*
* Will return true if the event's attendees match the roles passed in.
*
* The purpose is to test if this event should be marked with ATTENDING status for the visitor.
*
* @param event
* @param visitor
* @param owner
* @return true if and only if the event if is an available appointment with attendees that match the visitor and owner with the matching roles
*/
public boolean isAttendingMatch(VEvent event, IScheduleVisitor visitor, IScheduleOwner owner) {
// only test the appointment if it's marked as an available appointment
if(event.getProperties().contains(SchedulingAssistantAppointment.TRUE)) {
boolean visitorMatch = isAttendingAsVisitor(event, visitor.getCalendarAccount());
if(!visitorMatch) {
return false;
}
boolean ownerMatch = isAttendingAsOwner(event, owner.getCalendarAccount());
return ownerMatch;
}
return false;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#isAttendingAsVisitor(net.fortuna.ical4j.model.component.VEvent, org.jasig.schedassist.model.ICalendarAccount)
*/
@Override
public boolean isAttendingAsVisitor(VEvent event,
ICalendarAccount proposedVisitor) {
// only test the appointment if it's marked as an available appointment
if(event.getProperties().contains(SchedulingAssistantAppointment.TRUE)) {
PropertyList attendees = getAttendeeListFromEvent(event);
// walk through attendee list
for(Object obj : attendees) {
Property attendee = (Property) obj;
// extract UW APPOINTMENT_ROLE
Parameter p = attendee.getParameter(AppointmentRole.APPOINTMENT_ROLE);
if(null != p) {
AppointmentRole role = new AppointmentRole(p.getValue());
if(role.isVisitor() && attendeeMatchesPerson(attendee, proposedVisitor)) {
return true;
}
}
}
}
return false;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#isAttendingAsOwner(net.fortuna.ical4j.model.component.VEvent, org.jasig.schedassist.model.ICalendarAccount)
*/
@Override
public boolean isAttendingAsOwner(VEvent event,
ICalendarAccount proposedOwner) {
// only test the appointment if it's marked as an available appointment
if(event.getProperties().contains(SchedulingAssistantAppointment.TRUE)) {
Organizer organizer = (Organizer) event.getProperty(Organizer.ORGANIZER);
if(organizer == null) {
return false;
}
return attendeeMatchesPerson(organizer, proposedOwner);
}
return false;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#convertScheduleForReflection(org.jasig.schedassist.model.AvailableSchedule)
*/
@Override
public List<net.fortuna.ical4j.model.Calendar> convertScheduleForReflection(
final AvailableSchedule availableSchedule) {
if(availableSchedule.isEmpty()) {
return Collections.emptyList();
}
SortedSet<AvailableBlock> combinedBlocks = AvailableBlockBuilder.combine(availableSchedule.getAvailableBlocks());
Map<String, VEvent> summaryToEvent = new HashMap<String, VEvent>();
for(AvailableBlock block: combinedBlocks) {
String summary = constructSummaryValueForReflectionEvent(block);
VEvent event = summaryToEvent.get(summary);
if(event == null) {
event = convertBlockToReflectionEvent(block);
summaryToEvent.put(summary, event);
} else {
// add Rdate to existing event
net.fortuna.ical4j.model.Date start = new net.fortuna.ical4j.model.Date(DateUtils.truncate(block.getStartTime(), java.util.Calendar.DATE));
DateList dates = new DateList(Value.DATE);
dates.add(start);
RDate rDate = new RDate(dates);
event.getProperties().add(rDate);
}
}
List<net.fortuna.ical4j.model.Calendar> results = new ArrayList<net.fortuna.ical4j.model.Calendar>();
for(VEvent e: summaryToEvent.values()) {
ComponentList components = new ComponentList();
components.add(e);
results.add(wrapInternal(components));
}
return results;
}
/* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#wrapEventInCalendar(net.fortuna.ical4j.model.component.VEvent)
*/
@Override
public net.fortuna.ical4j.model.Calendar wrapEventInCalendar(VEvent event) {
ComponentList components = new ComponentList();
components.add(event);
net.fortuna.ical4j.model.Calendar result = wrapInternal(components);
return result;
}
/**
*
* @param components
* @return
*/
protected net.fortuna.ical4j.model.Calendar wrapInternal(ComponentList components) {
net.fortuna.ical4j.model.Calendar result = new net.fortuna.ical4j.model.Calendar(components);
result.getProperties().add(Version.VERSION_2_0);
result.getProperties().add(PROD_ID);
return result;
}
/* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#generateNewUid()
*/
@Override
public Uid generateNewUid() {
UUID uuid = UUID.randomUUID();
Uid result = new Uid(uuid.toString());
return result;
}
/**
* Convert the {@link AvailableBlock} into a {@link VEvent} that can be stored in the
* calendar system to represent that same block.
*
* This event MUST not cause conflicts.
*
* @param block
* @return an appropriate event
*/
protected VEvent convertBlockToReflectionEvent(final AvailableBlock block) {
Date blockStartTime = DateUtils.truncate(block.getStartTime(), Calendar.DATE);
DtStart start = new DtStart(new net.fortuna.ical4j.model.Date(blockStartTime));
DtStamp stamp = new DtStamp(new net.fortuna.ical4j.model.DateTime(new Date()));
Date blockEndTime = DateUtils.addDays(blockStartTime, 1);
DtEnd end = new DtEnd(new net.fortuna.ical4j.model.Date(blockEndTime));
PropertyList properties = new PropertyList();
properties.add(new Summary(constructSummaryValueForReflectionEvent(block)));
properties.add(start);
properties.add(stamp);
properties.add(end);
properties.add(new Created(new DateTime(new Date())));
properties.add(new LastModified(new DateTime(new Date())));
properties.add(Clazz.PRIVATE);
properties.add(new Sequence(0));
properties.add(Transp.TRANSPARENT);
if(StringUtils.isNotBlank(block.getMeetingLocation())) {
properties.add(new Location(block.getMeetingLocation()));
}
properties.add(AvailabilityReflection.TRUE);
VEvent event = new VEvent(properties);
return event;
}
/**
*
* @param block
* @return
*/
protected String constructSummaryValueForReflectionEvent(final AvailableBlock block) {
SimpleDateFormat df = new SimpleDateFormat("h:mm a");
StringBuilder summary = new StringBuilder();
summary.append("Available ");
summary.append(df.format(block.getStartTime()));
summary.append(" - ");
summary.append(df.format(block.getEndTime()));
return summary.toString();
}
/**
* Helper method to convert the {@link Date} into the
* {@link String} representation required for iCalendar.
*
* The returned date/time will be in the UTC timezone.
*
* @param date
* @return the date as a string
* @throws IllegalArgumentException if the date argument was null
*/
public static String convertToICalendarFormat(final Date date) {
Validate.notNull(date, "cannot format null date");
return FASTDATEFORMAT.format(DateUtils.truncate(date, Calendar.SECOND));
}
/**
*
* @param attendee
* @return true if if the attendee property has PARSTAT=NEEDS_ACTION
*/
public static boolean isPartStatNeedsAction(final Property attendee) {
Validate.notNull(attendee);
Parameter p = attendee.getParameter(PartStat.PARTSTAT);
if(PartStat.NEEDS_ACTION.equals(p)) {
return true;
}
return false;
}
/**
* 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
*/
public static URI emailToURI(final String emailAddress) {
Validate.notEmpty(emailAddress, "emailAddress cannot be null");
try {
return new URI("mailto:" + emailAddress);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("caught URISyntaxException trying to construct mailto URI for " + emailAddress, e);
}
}
/* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#getEventVisitorLimit(net.fortuna.ical4j.model.component.VEvent)
*/
@Override
public Integer getEventVisitorLimit(VEvent event) {
if(event == null) {
return null;
}
Property limit = event.getProperty(VisitorLimit.VISITOR_LIMIT);
if(limit != null) {
return Integer.parseInt(limit.getValue());
}
return null;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#isEventRecurring(net.fortuna.ical4j.model.component.VEvent)
*/
@Override
public boolean isEventRecurring(VEvent event) {
return event.getProperties(RDate.RDATE).size() > 0 || event.getProperties(RRule.RRULE).size() > 0;
}
/*
* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#expandRecurrence(net.fortuna.ical4j.model.component.VEvent, java.util.Date, java.util.Date)
*/
@Override
public PeriodList calculateRecurrence(VEvent event,
Date startBoundary, Date endBoundary) {
Period period = new Period(new DateTime(startBoundary), new DateTime(endBoundary));
PeriodList periodList = event.calculateRecurrenceSet(period);
PeriodList results = new PeriodList();
for(Object o: periodList) {
Period p = (Period) o;
if(isAllDayPeriod(p)) {
// this period is broken
// the Periods returned by ical4j's calculateRecurrenceSet have range start/ends that are off by the system default's timezone offset
TimeZone systemTimezone = java.util.TimeZone.getDefault();
int offset = systemTimezone.getOffset(p.getStart().getTime());
Period fixed = new Period(
new DateTime(DateUtils.addMilliseconds(p.getRangeStart(), -offset)),
new DateTime(DateUtils.addMilliseconds(p.getRangeEnd(), -offset)));
results.add(fixed);
} else {
results.add(p);
}
}
return results;
}
protected boolean isAllDayPeriod(Period period) {
Dur duration = period.getDuration();
return duration.getDays() == 1 && duration.getHours() == 0 && duration.getMinutes() == 0;
}
/**
*
* @param event
* @return
*/
protected boolean isAllDayEvent(VEvent event) {
return Value.DATE.equals(event.getStartDate().getParameter(Value.VALUE));
}
/* (non-Javadoc)
* @see org.jasig.schedassist.model.IEventUtils#extractUid(net.fortuna.ical4j.model.Calendar)
*/
@Override
public Uid extractUid(net.fortuna.ical4j.model.Calendar calendar) {
ComponentList components = calendar.getComponents();
Uid unique = null;
for(Iterator<?> i = components.iterator(); i.hasNext();) {
Component component = (Component) i.next();
if(VEvent.VEVENT.equals(component.getName())) {
Uid uid = ((VEvent) component).getUid();
if(unique == null) {
unique = uid;
} else if (!unique.equals(uid) && uid != null) {
LOG.info("extractUid encountered a calendar that has 2 (or more) distinct UID values (" + unique.getValue() + ", " + uid.getValue() +")");
return null;
}
}
}
return unique;
}
}