/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
*/
package org.olat.commons.calendar.manager;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
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.StringTokenizer;
import java.util.UUID;
import org.apache.commons.lang.RandomStringUtils;
import org.olat.basesecurity.IdentityRef;
import org.olat.commons.calendar.CalendarManagedFlag;
import org.olat.commons.calendar.CalendarManager;
import org.olat.commons.calendar.CalendarModule;
import org.olat.commons.calendar.CalendarUtils;
import org.olat.commons.calendar.model.CalendarKey;
import org.olat.commons.calendar.model.CalendarUserConfiguration;
import org.olat.commons.calendar.model.Kalendar;
import org.olat.commons.calendar.model.KalendarEvent;
import org.olat.commons.calendar.model.KalendarEventKey;
import org.olat.commons.calendar.model.KalendarEventLink;
import org.olat.commons.calendar.model.KalendarRecurEvent;
import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.FileUtils;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.cache.CacheWrapper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.coordinate.SyncerCallback;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.ICourse;
import org.olat.group.BusinessGroup;
import org.olat.user.UserManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Parameter;
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.Recur;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.Contact;
import net.fortuna.ical4j.model.property.Created;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.model.property.Duration;
import net.fortuna.ical4j.model.property.ExDate;
import net.fortuna.ical4j.model.property.LastModified;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.RRule;
import net.fortuna.ical4j.model.property.RecurrenceId;
import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Url;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.model.property.XProperty;
@Service
public class ICalFileCalendarManager implements CalendarManager, InitializingBean {
private static final OLog log = Tracing.createLoggerFor(ICalFileCalendarManager.class);
private File fStorageBase;
// o_clusterOK by:cg
private CacheWrapper<String, Kalendar> calendarCache;
private static final Clazz ICAL_CLASS_PRIVATE = new Clazz("PRIVATE");
private static final Clazz ICAL_CLASS_PUBLIC = new Clazz("PUBLIC");
private static final Clazz ICAL_CLASS_X_FREEBUSY = new Clazz("X-FREEBUSY");
/** rule for recurring events */
private static final String ICAL_RRULE = "RRULE";
/** property to exclude events from recurrence */
private static final String ICAL_EXDATE = "EXDATE";
private TimeZone tz;
@Autowired
private UserManager userManager;
@Autowired
private CalendarModule calendarModule;
@Autowired
private CalendarUserConfigurationDAO calendarUserConfigDao;
@Override
public void afterPropertiesSet() {
fStorageBase = new File(WebappHelper.getUserDataRoot(), "calendars");
if (!fStorageBase.exists()) {
if (!fStorageBase.mkdirs())
throw new OLATRuntimeException("Error creating calendar base directory at: " + fStorageBase.getAbsolutePath(), null);
}
//create the directories
String[] dirs = new String[]{ TYPE_USER, TYPE_GROUP, TYPE_COURSE };
for(String dir:dirs) {
File fDirectory = new File(fStorageBase, dir);
if(!fDirectory.exists()) {
fDirectory.mkdirs();
}
}
// set parser to relax (needed for allday events
// see http://sourceforge.net/forum/forum.php?thread_id=1253735&forum_id=368291
// made in module System.setProperty("ical4j.unfolding.relaxed", "true");
// initialize timezone
tz = calendarModule.getDefaultTimeZone();
calendarCache = CoordinatorManager.getInstance().getCoordinator().getCacher().getCache(CalendarManager.class.getSimpleName(), "calendar");
}
/**
* Check if a calendar already exists for the given id.
* @param calendarID
* @param type
* @return
*/
@Override
public boolean calendarExists(String calendarType, String calendarID) {
return getCalendarFile(calendarType, calendarID).exists();
}
/**
*
* @see org.olat.calendar.CalendarManager#createClaendar(java.lang.String)
*/
@Override
public Kalendar createCalendar(String type, String calendarID) {
return new Kalendar(calendarID, type);
}
@Override
public Kalendar getCalendar(final String type, final String calendarID) {
String key = getKeyFor(type, calendarID);
Kalendar cal = calendarCache.get(key);
if(cal == null) {
cal = getCalendarFromCache(type, calendarID);
}
return cal;
}
private Kalendar getCalendarFromCache(final String callType, final String callCalendarID) {
String calKey = getKeyFor(callType,callCalendarID);
Kalendar cal = calendarCache.get(calKey);
if (cal == null) {
cal = loadOrCreateCalendar(callType, callCalendarID);
Kalendar cacheCal = calendarCache.putIfAbsent(calKey, cal);
if(cacheCal != null) {
cal = cacheCal;
}
}
return cal;
}
/**
* {@inheritDoc}
*/
@Override
public OLATResourceable getOresHelperFor(Kalendar cal) {
return OresHelper.createOLATResourceableType(getKeyFor(cal.getType(), cal.getCalendarID()));
}
private String getKeyFor(String type, String calendarID) {
return type + "_" + calendarID;
}
/**
* Internal load calendar file from filesystem.
*/
// o_clusterOK by:cg This must not be synchronized because the caller already synchronized
private Kalendar loadCalendarFromFile(String type, String calendarID) {
Calendar calendar = readCalendar(type, calendarID);
return createKalendar(type, calendarID, calendar);
}
protected Kalendar createKalendar(String type, String calendarID, Calendar calendar) {
Kalendar cal = new Kalendar(calendarID, type);
for (Iterator<?> iter = calendar.getComponents().iterator(); iter.hasNext();) {
Object comp = iter.next();
if (comp instanceof VEvent) {
VEvent vevent = (VEvent)comp;
KalendarEvent calEvent = getKalendarEvent(vevent);
cal.addEvent(calEvent);
} else if (comp instanceof VTimeZone) {
log.info("createKalendar: VTimeZone Component is not supported and will not be added to calender");
log.debug("createKalendar: VTimeZone=" + comp);
} else {
log.warn("createKalendar: unknown Component=" + comp);
}
}
return cal;
}
/**
* Internal read calendar file from filesystem. It doesn't
* use the cache and return a not shared calendar.
*/
@Override
public Calendar readCalendar(String type, String calendarID) {
if(log.isDebug()) {
log.debug("readCalendar from file, type=" + type + " calendarID=" + calendarID);
}
File calendarFile = getCalendarFile(type, calendarID);
return readCalendar(calendarFile);
}
@Override
public Calendar readCalendar(File calendarFile) {
try(InputStream fIn = new FileInputStream(calendarFile);
InputStream in = new BufferedInputStream(fIn)) {
CalendarBuilder builder = new CalendarBuilder();
return builder.build(in);
} catch (FileNotFoundException fne) {
throw new OLATRuntimeException("Not found: " + calendarFile, fne);
} catch (Exception e) {
throw new OLATRuntimeException("Error parsing calendar file.", e);
}
}
@Override
public Kalendar buildKalendarFrom(InputStream in, String calType, String calId) {
Kalendar kalendar = null;
try(BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
CalendarBuilder builder = new CalendarBuilder();
Calendar calendar = builder.build(reader);
kalendar = createKalendar(calType, calId, calendar);
} catch (Exception e) {
throw new OLATRuntimeException("Error parsing calendar file.", e);
}
return kalendar;
}
@Override
public boolean synchronizeCalendarFrom(InputStream in, String source, Kalendar targetCalendar) {
try(BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
Calendar calendar = new CalendarBuilder().build(reader);
Kalendar tmpKalendar = createKalendar("TEMP", UUID.randomUUID().toString(), calendar);
OLATResourceable calOres = getOresHelperFor(targetCalendar);
Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
//remove event in target calendar which doesn't exist in stream
Collection<KalendarEvent> currentEvents = targetCalendar.getEvents();
for(KalendarEvent currentEvent:currentEvents) {
if(currentEvent.getExternalSource() != null && source.equals(currentEvent.getExternalSource())) {
String eventId = currentEvent.getID();
String recurrenceId = currentEvent.getRecurrenceID();
if(tmpKalendar.getEvent(eventId, recurrenceId) == null) {
targetCalendar.removeEvent(currentEvent);
}
}
}
//
for(KalendarEvent event:tmpKalendar.getEvents()) {
event.setManagedFlags(new CalendarManagedFlag[]{ CalendarManagedFlag.all } );
event.setExternalSource(source);
KalendarEvent currentEvent = targetCalendar.getEvent(event.getID(), event.getRecurrenceID());
if(currentEvent == null) {
targetCalendar.addEvent(event);
} else {
//need perhaps more refined synchronization per event
targetCalendar.addEvent(event);
}
}
boolean successfullyPersist = persistCalendar(targetCalendar);
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus()
.fireEventToListenersOf(new CalendarGUIModifiedEvent(targetCalendar), OresHelper.lookupType(CalendarManager.class));
return new Boolean(successfullyPersist);
}
});
return updatedSuccessful.booleanValue();
} catch (Exception e) {
log.error("", e);
return false;
}
}
/**
* Save a calendar.
* This method is not thread-safe. Must be called from a synchronized block.
* Be sure to have the newest calendar (reload calendar in synchronized block before safe it).
* @param calendar
*/
// o_clusterOK by:cg only called by Junit-test
@Override
public boolean persistCalendar(Kalendar kalendar) {
Calendar calendar = buildCalendar(kalendar);
boolean success = writeCalendarFile(calendar,kalendar.getType(), kalendar.getCalendarID());
calendarCache.update(getKeyFor(kalendar.getType(), kalendar.getCalendarID()), kalendar);
return success;
}
private boolean writeCalendarFile(Calendar calendar, String calType, String calId) {
File fKalendarFile = getCalendarFile(calType, calId);
OutputStream os = null;
try {
os = new BufferedOutputStream(new FileOutputStream(fKalendarFile, false));
CalendarOutputter calOut = new CalendarOutputter(false);
calOut.output(calendar, os);
} catch (Exception e) {
return false;
} finally {
FileUtils.closeSafely(os);
}
return true;
}
/**
* Delete calendar by type and id.
*/
@Override
public boolean deleteCalendar(String type, String calendarID) {
calendarCache.remove( getKeyFor(type,calendarID) );
File fKalendarFile = getCalendarFile(type, calendarID);
return fKalendarFile.delete();
}
@Override
public File getCalendarICalFile(String type, String calendarID) {
File fCalendarICalFile = getCalendarFile(type, calendarID);
if (fCalendarICalFile.exists()) return fCalendarICalFile;
else return null;
}
@Override
public CalendarUserConfiguration findCalendarConfigForIdentity(Kalendar calendar, IdentityRef identity) {
return calendarUserConfigDao.getCalendarUserConfiguration(identity, calendar.getCalendarID(), calendar.getType());
}
@Override
public void saveCalendarConfigForIdentity(KalendarRenderWrapper wrapper, Identity identity) {
Kalendar calendar = wrapper.getKalendar();
CalendarUserConfiguration configuration = calendarUserConfigDao
.getCalendarUserConfiguration(identity, calendar.getCalendarID(), calendar.getType());
if(configuration == null) {
configuration = calendarUserConfigDao.createCalendarUserConfiguration(wrapper.getKalendar(), identity,
wrapper.getToken(), wrapper.isInAggregatedFeed(), wrapper.isVisible());
} else {
configuration.setVisible(wrapper.isVisible());
configuration.setCssClass(wrapper.getCssClass());
configuration.setToken(wrapper.getToken());
configuration.setInAggregatedFeed(wrapper.isInAggregatedFeed());
configuration = calendarUserConfigDao.update(configuration);
}
}
@Override
public CalendarUserConfiguration createAggregatedCalendarConfig(Identity identity) {
String token = RandomStringUtils.randomAlphanumeric(6);
Kalendar calendar = new Kalendar(identity.getKey().toString(), CalendarManager.TYPE_USER_AGGREGATED);
return calendarUserConfigDao.createCalendarUserConfiguration(calendar, identity,
token, false, false);
}
@Override
public CalendarUserConfiguration saveCalendarConfig(CalendarUserConfiguration configuration) {
return calendarUserConfigDao.update(configuration);
}
@Override
public CalendarUserConfiguration getCalendarUserConfiguration(Long key) {
return calendarUserConfigDao.getCalendarUserConfiguration(key);
}
@Override
public List<CalendarUserConfiguration> getCalendarUserConfigurationsList(IdentityRef identity, String... types) {
return calendarUserConfigDao.getCalendarUserConfigurations(identity, types);
}
@Override
public Map<CalendarKey,CalendarUserConfiguration> getCalendarUserConfigurationsMap(IdentityRef identity, String... types) {
List<CalendarUserConfiguration> list = calendarUserConfigDao.getCalendarUserConfigurations(identity, types);
Map<CalendarKey,CalendarUserConfiguration> map = new HashMap<>();
for(CalendarUserConfiguration config:list) {
map.put(new CalendarKey(config.getCalendarId(), config.getType()), config);
}
return map;
}
@Override
public String getCalendarToken(String calendarType, String calendarID, String userName) {
return calendarUserConfigDao.getCalendarToken(calendarType, calendarID, userName);
}
protected Calendar buildCalendar(Kalendar kalendar) {
Calendar calendar = new Calendar();
// add standard propeties
calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
calendar.getProperties().add(Version.VERSION_2_0);
calendar.getProperties().add(CalScale.GREGORIAN);
for (Iterator<KalendarEvent> iter = kalendar.getEvents().iterator(); iter.hasNext();) {
KalendarEvent kEvent = iter.next();
VEvent vEvent = getVEvent(kEvent);
calendar.getComponents().add(vEvent);
}
return calendar;
}
@Override
public KalendarEvent createKalendarEventRecurringOccurence(KalendarRecurEvent recurEvent) {
KalendarEvent rootEvent = recurEvent.getCalendar().getEvent(recurEvent.getID(), null);
VEvent vEvent = getVEvent(recurEvent);
PropertyList vEventProperties = vEvent.getProperties();
for(Iterator<?> objIt=vEventProperties.iterator(); objIt.hasNext(); ) {
Object property = objIt.next();
if(property instanceof RRule || property instanceof ExDate) {
objIt.remove();
}
}
try {
Kalendar calendar = recurEvent.getCalendar();
Date startDate = recurEvent.getOccurenceDate();
String startString = CalendarUtils.formatRecurrenceDate(startDate, rootEvent.isAllDayEvent());
RecurrenceId recurId;
if(rootEvent.isAllDayEvent()) {
recurId = new RecurrenceId(tz);
recurId.setDate(CalendarUtils.createDate(startDate));
} else {
recurId = new RecurrenceId(startString, tz);
}
vEventProperties.add(recurId);
KalendarEvent kEvent = getKalendarEvent(vEvent);
kEvent.setKalendar(calendar);
return kEvent;
} catch (ParseException e) {
log.error("", e);
return null;
}
}
private VEvent getVEvent(KalendarEvent kEvent) {
VEvent vEvent = new VEvent();
if (!kEvent.isAllDayEvent()) {
// regular VEvent
DateTime dtBegin = new DateTime(kEvent.getBegin());
if(tz != null) {
dtBegin.setTimeZone(tz);
}
Date kEventEnd = kEvent.getEnd();
if(kEventEnd == null) {
vEvent = new VEvent(dtBegin, kEvent.getSubject());
} else {
DateTime dtEnd = new DateTime(kEventEnd);
if(tz != null) {
dtEnd.setTimeZone(tz);
}
vEvent = new VEvent(dtBegin, dtEnd, kEvent.getSubject());
}
} else {
// AllDay VEvent
net.fortuna.ical4j.model.Date dtBegin = CalendarUtils.createDate(kEvent.getBegin());
// adjust end date: ICal end dates for all day events are on the next day
Date adjustedEndDate = new Date(kEvent.getEnd().getTime() + (1000 * 60 * 60 * 24));
net.fortuna.ical4j.model.Date dtEnd = CalendarUtils.createDate(adjustedEndDate);
vEvent = new VEvent(dtBegin, dtEnd, kEvent.getSubject());
}
if(kEvent.getCreated() > 0) {
Created created = new Created(new DateTime(kEvent.getCreated()));
vEvent.getProperties().add(created);
}
if( (kEvent.getCreatedBy() != null) && !kEvent.getCreatedBy().trim().isEmpty()) {
Contact contact = new Contact();
contact.setValue(kEvent.getCreatedBy());
vEvent.getProperties().add(contact);
}
if(kEvent.getLastModified() > 0) {
LastModified lastMod = new LastModified(new DateTime(kEvent.getLastModified()));
vEvent.getProperties().add(lastMod);
}
// Uid
PropertyList vEventProperties = vEvent.getProperties();
vEventProperties.add(new Uid(kEvent.getID()));
// clazz
switch (kEvent.getClassification()) {
case KalendarEvent.CLASS_PRIVATE: vEventProperties.add(ICAL_CLASS_PRIVATE); break;
case KalendarEvent.CLASS_PUBLIC: vEventProperties.add(ICAL_CLASS_PUBLIC); break;
case KalendarEvent.CLASS_X_FREEBUSY: vEventProperties.add(ICAL_CLASS_X_FREEBUSY); break;
default: vEventProperties.add(ICAL_CLASS_PRIVATE); break;
}
// location
if (kEvent.getLocation() != null) {
vEventProperties.add(new Location(kEvent.getLocation()));
}
if(kEvent.getDescription() != null) {
vEventProperties.add(new Description(kEvent.getDescription()));
}
// event links
Url urlOnce = null;
List<KalendarEventLink> kalendarEventLinks = kEvent.getKalendarEventLinks();
if ((kalendarEventLinks != null) && !kalendarEventLinks.isEmpty()) {
for (Iterator<KalendarEventLink> iter = kalendarEventLinks.iterator(); iter.hasNext();) {
KalendarEventLink link = iter.next();
StringBuilder linkEncoded = new StringBuilder(200);
linkEncoded.append(link.getProvider());
linkEncoded.append("§");
linkEncoded.append(link.getId());
linkEncoded.append("§");
linkEncoded.append(link.getDisplayName());
linkEncoded.append("§");
linkEncoded.append(link.getURI());
linkEncoded.append("§");
linkEncoded.append(link.getIconCssClass());
XProperty linkProperty = new XProperty(ICAL_X_OLAT_LINK, linkEncoded.toString());
vEventProperties.add(linkProperty);
if(urlOnce == null) {
try {
Url url = new Url();
url.setValue(link.getURI());
urlOnce = url;
} catch (URISyntaxException e) {
log.error("Invalid URL:" + link.getURI());
}
}
}
}
if(urlOnce != null) {
vEventProperties.add(urlOnce);
}
if (kEvent.getComment() != null) {
vEventProperties.add(new XProperty(ICAL_X_OLAT_COMMENT, kEvent.getComment()));
}
if (kEvent.getNumParticipants() != null) {
vEventProperties.add(new XProperty(ICAL_X_OLAT_NUMPARTICIPANTS, Integer.toString(kEvent.getNumParticipants())));
}
if (kEvent.getParticipants() != null) {
StringBuilder strBuf = new StringBuilder();
String[] participants = kEvent.getParticipants();
for ( String participant : participants ) {
strBuf.append(participant);
strBuf.append("§");
}
vEventProperties.add(new XProperty(ICAL_X_OLAT_PARTICIPANTS, strBuf.toString()));
}
if (kEvent.getSourceNodeId() != null) {
vEventProperties.add(new XProperty(ICAL_X_OLAT_SOURCENODEID, kEvent.getSourceNodeId()));
}
if(kEvent.getManagedFlags() != null) {
String val = CalendarManagedFlag.toString(kEvent.getManagedFlags());
vEventProperties.add(new XProperty(ICAL_X_OLAT_MANAGED, val));
}
if(StringHelper.containsNonWhitespace(kEvent.getExternalId())) {
vEventProperties.add(new XProperty(ICAL_X_OLAT_EXTERNAL_ID, kEvent.getExternalId()));
}
if(StringHelper.containsNonWhitespace(kEvent.getExternalSource())) {
vEventProperties.add(new XProperty(ICAL_X_OLAT_EXTERNAL_SOURCE, kEvent.getExternalSource()));
}
String recurenceId = kEvent.getRecurrenceID();
if(StringHelper.containsNonWhitespace(recurenceId)) {
try {
RecurrenceId recurId = new RecurrenceId(tz);
// VALUE=DATE recurrence id need to be specially saved
if(recurenceId.length() < 9) {
recurId = new RecurrenceId(tz);
recurId.setDate(CalendarUtils.createDate(new net.fortuna.ical4j.model.Date(recurenceId)));
} else {
recurId = new RecurrenceId(recurenceId, tz);
}
vEventProperties.add(recurId);
} catch (ParseException e) {
log.error("cannot create recurrence ID: " + recurenceId, e);
}
}
// recurrence
String recurrence = kEvent.getRecurrenceRule();
if(recurrence != null && !recurrence.equals("")) {
try {
Recur recur = new Recur(recurrence);
RRule rrule = new RRule(recur);
vEventProperties.add(rrule);
} catch (ParseException e) {
log.error("cannot create recurrence rule: " + recurrence.toString(), e);
}
}
// recurrence exclusions
String recurrenceExc = kEvent.getRecurrenceExc();
if(recurrenceExc != null && !recurrenceExc.equals("")) {
ExDate exdate = new ExDate();
try {
exdate.setValue(recurrenceExc);
vEventProperties.add(exdate);
} catch (ParseException e) {
log.error("", e);
}
}
return vEvent;
}
/**
* Build a KalendarEvent out of a source VEvent.
* @param event
* @return
*/
private KalendarEvent getKalendarEvent(VEvent event) {
// subject
Summary eventsummary = event.getSummary();
String subject = "";
if (eventsummary != null)
subject = eventsummary.getValue();
// start
DtStart dtStart = event.getStartDate();
Date start = dtStart.getDate();
Duration dur = event.getDuration();
// end
Date end = null;
if (dur != null) {
end = dur.getDuration().getTime(start);
} else if(event.getEndDate() != null) {
end = event.getEndDate().getDate();
}
// check all day event first
boolean isAllDay = false;
Parameter dateParameter = event.getProperties().getProperty(Property.DTSTART).getParameters().getParameter(Value.DATE.getName());
if (dateParameter != null) isAllDay = true;
if (isAllDay) {
//Make sure the time of the dates are 00:00 localtime because DATE fields in iCal are GMT 00:00
//Note that start date and end date can have different offset because of daylight saving switch
java.util.TimeZone timezone = java.util.GregorianCalendar.getInstance().getTimeZone();
start = new Date(start.getTime() - timezone.getOffset(start.getTime()));
end = new Date(end.getTime() - timezone.getOffset(end.getTime()));
// adjust end date: ICal sets end dates to the next day
end = new Date(end.getTime() - (1000 * 60 * 60 * 24));
}
Uid eventuid = event.getUid();
String uid;
if (eventuid != null) {
uid = eventuid.getValue();
} else {
uid = CodeHelper.getGlobalForeverUniqueID();
}
RecurrenceId eventRecurenceId = event.getRecurrenceId();
String recurrenceId = null;
if(eventRecurenceId != null) {
recurrenceId = eventRecurenceId.getValue();
}
KalendarEvent calEvent = new KalendarEvent(uid, recurrenceId, subject, start, end);
calEvent.setAllDayEvent(isAllDay);
// classification
Clazz classification = event.getClassification();
if (classification != null) {
String sClass = classification.getValue();
int iClassification = KalendarEvent.CLASS_PRIVATE;
if (sClass.equals(ICAL_CLASS_PRIVATE.getValue())) iClassification = KalendarEvent.CLASS_PRIVATE;
else if (sClass.equals(ICAL_CLASS_X_FREEBUSY.getValue())) iClassification = KalendarEvent.CLASS_X_FREEBUSY;
else if (sClass.equals(ICAL_CLASS_PUBLIC.getValue())) iClassification = KalendarEvent.CLASS_PUBLIC;
calEvent.setClassification(iClassification);
}
// created/last modified
Created created = event.getCreated();
if (created != null) {
calEvent.setCreated(created.getDate().getTime());
}
// created/last modified
Contact contact = (Contact)event.getProperty(Property.CONTACT);
if (contact != null) {
calEvent.setCreatedBy(contact.getValue());
}
LastModified lastModified = event.getLastModified();
if (lastModified != null) {
calEvent.setLastModified(lastModified.getDate().getTime());
}
Description description = event.getDescription();
if(description != null) {
calEvent.setDescription(description.getValue());
}
// location
Location location = event.getLocation();
if (location != null) {
calEvent.setLocation(location.getValue());
}
// links if any
PropertyList linkProperties = event.getProperties(ICAL_X_OLAT_LINK);
List<KalendarEventLink> kalendarEventLinks = new ArrayList<KalendarEventLink>();
for (Iterator<?> iter = linkProperties.iterator(); iter.hasNext();) {
XProperty linkProperty = (XProperty) iter.next();
if (linkProperty != null) {
String encodedLink = linkProperty.getValue();
StringTokenizer st = new StringTokenizer(encodedLink, "§", false);
if (st.countTokens() >= 4) {
String provider = st.nextToken();
String id = st.nextToken();
String displayName = st.nextToken();
String uri = st.nextToken();
String iconCss = "";
// migration: iconCss has been added later, check if available first
if (st.hasMoreElements()) {
iconCss = st.nextToken();
}
KalendarEventLink eventLink = new KalendarEventLink(provider, id, displayName, uri, iconCss);
kalendarEventLinks.add(eventLink);
}
}
}
calEvent.setKalendarEventLinks(kalendarEventLinks);
Property comment = event.getProperty(ICAL_X_OLAT_COMMENT);
if (comment != null)
calEvent.setComment(comment.getValue());
Property numParticipants = event.getProperty(ICAL_X_OLAT_NUMPARTICIPANTS);
if (numParticipants != null)
calEvent.setNumParticipants(Integer.parseInt(numParticipants.getValue()));
Property participants = event.getProperty(ICAL_X_OLAT_PARTICIPANTS);
if (participants != null) {
StringTokenizer strTok = new StringTokenizer(participants.getValue(), "§", false);
String[] parts = new String[strTok.countTokens()];
for ( int i = 0; strTok.hasMoreTokens(); i++ ) {
parts[i] = strTok.nextToken();
}
calEvent.setParticipants(parts);
}
Property sourceNodId = event.getProperty(ICAL_X_OLAT_SOURCENODEID);
if (sourceNodId != null) {
calEvent.setSourceNodeId(sourceNodId.getValue());
}
//managed properties
Property managed = event.getProperty(ICAL_X_OLAT_MANAGED);
if(managed != null) {
String value = managed.getValue();
if("true".equals(value)) {
value = "all";
}
CalendarManagedFlag[] values = CalendarManagedFlag.toEnum(value);
calEvent.setManagedFlags(values);
}
Property externalId = event.getProperty(ICAL_X_OLAT_EXTERNAL_ID);
if(externalId != null) {
calEvent.setExternalId(externalId.getValue());
}
Property externalSource = event.getProperty(ICAL_X_OLAT_EXTERNAL_SOURCE);
if(externalSource != null) {
calEvent.setExternalSource(externalSource.getValue());
}
// recurrence
if (event.getProperty(ICAL_RRULE) != null) {
calEvent.setRecurrenceRule(event.getProperty(ICAL_RRULE).getValue());
}
// recurrence exclusions
if (event.getProperty(ICAL_EXDATE) != null) {
calEvent.setRecurrenceExc(event.getProperty(ICAL_EXDATE).getValue());
}
return calEvent;
}
@Override
public KalendarEvent getRecurringInPeriod(Date periodStart, Date periodEnd, KalendarEvent kEvent) {
boolean isRecurring= isRecurringInPeriod(periodStart, periodEnd, kEvent);
KalendarEvent recurEvent = null;
if(isRecurring) {
java.util.Calendar periodStartCal = java.util.Calendar.getInstance();
java.util.Calendar eventBeginCal = java.util.Calendar.getInstance();
periodStartCal.setTime(periodStart);
eventBeginCal.setTime(kEvent.getBegin());
Long duration = kEvent.getEnd().getTime() - kEvent.getBegin().getTime();
java.util.Calendar beginCal = java.util.Calendar.getInstance();
beginCal.setTime(kEvent.getBegin());
beginCal.set(java.util.Calendar.YEAR, periodStartCal.get(java.util.Calendar.YEAR));
beginCal.set(java.util.Calendar.MONTH, periodStartCal.get(java.util.Calendar.MONTH));
beginCal.set(java.util.Calendar.DAY_OF_MONTH, periodStartCal.get(java.util.Calendar.DAY_OF_MONTH));
recurEvent = kEvent.clone();
recurEvent.setBegin(beginCal.getTime());
recurEvent.setEnd(new Date(beginCal.getTime().getTime() + duration));
}
return recurEvent;
}
@Override
public boolean isRecurringInPeriod(Date periodStart, Date periodEnd, KalendarEvent kEvent) {
DateList recurDates = getRecurringsInPeriod(periodStart, periodEnd, kEvent);
return (recurDates != null && !recurDates.isEmpty());
}
@Override
public File getCalendarFile(String type, String calendarID) {
return new File(fStorageBase, "/" + type + "/" + calendarID + ".ics");
}
@Override
public KalendarRenderWrapper getPersonalCalendar(Identity identity) {
Kalendar cal = getCalendar(CalendarManager.TYPE_USER, identity.getName());
String fullName = userManager.getUserDisplayName(identity);
KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, fullName);
calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_BLUE);
calendarWrapper.setVisible(true);
return calendarWrapper;
}
@Override
public KalendarRenderWrapper getImportedCalendar(Identity identity, String calendarId) {
Kalendar cal = getCalendar(CalendarManager.TYPE_USER, calendarId);
KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, calendarId);
calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_BLUE);
calendarWrapper.setVisible(true);
calendarWrapper.setImported(true);
return calendarWrapper;
}
@Override
public KalendarRenderWrapper getGroupCalendar(BusinessGroup businessGroup) {
Kalendar cal = getCalendar(CalendarManager.TYPE_GROUP, businessGroup.getResourceableId().toString());
KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, businessGroup.getName());
calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_ORANGE);
calendarWrapper.setVisible(true);
return calendarWrapper;
}
@Override
public KalendarRenderWrapper getCourseCalendar(ICourse course) {
Kalendar cal = getCalendar(CalendarManager.TYPE_COURSE, course.getResourceableId().toString());
KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, course.getCourseTitle());
calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_GREEN);
calendarWrapper.setVisible(true);
return calendarWrapper;
}
@Override
public KalendarRenderWrapper getCalendarForDeletion(OLATResourceable resource) {
String type;
if("CourseModule".equals(resource.getResourceableTypeName())) {
type = CalendarManager.TYPE_COURSE;
} else {
type = CalendarManager.TYPE_GROUP;
}
Kalendar cal = getCalendar(type, resource.getResourceableId().toString());
KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, "To delete");
calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_GREEN);
calendarWrapper.setVisible(true);
return calendarWrapper;
}
@Override
public void deletePersonalCalendar(Identity identity) {
deleteCalendar(CalendarManager.TYPE_USER, identity.getName());
}
@Override
public void deleteGroupCalendar(BusinessGroup businessGroup) {
deleteCalendar(CalendarManager.TYPE_GROUP, businessGroup.getResourceableId().toString());
}
@Override
public void deleteCourseCalendar(ICourse course) {
deleteCalendar(CalendarManager.TYPE_COURSE, course.getResourceableId().toString());
}
@Override
public void deleteCourseCalendar(OLATResourceable course) {
deleteCalendar(CalendarManager.TYPE_COURSE, course.getResourceableId().toString());
}
@Override
public boolean addEventTo(final Kalendar cal, final KalendarEvent kalendarEvent) {
return addEventTo(cal, Collections.singletonList(kalendarEvent));
}
@Override
public boolean addEventTo(final Kalendar cal, final List<KalendarEvent> kalendarEvents) {
OLATResourceable calOres = getOresHelperFor(cal);
Boolean persistSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
Kalendar loadedCal = getCalendarFromCache(cal.getType(),cal.getCalendarID());
for(KalendarEvent kalendarEvent:kalendarEvents) {
loadedCal.addEvent(kalendarEvent);
}
boolean successfullyPersist = persistCalendar(loadedCal);
return new Boolean(successfullyPersist);
}
});
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class));
return persistSuccessful.booleanValue();
}
/**
* @see org.olat.commons.calendar.CalendarManager#removeEventFrom(org.olat.commons.calendar.model.Kalendar, org.olat.commons.calendar.model.KalendarEvent)
*/
@Override
public boolean removeEventFrom(final Kalendar cal, final KalendarEvent kalendarEvent) {
OLATResourceable calOres = getOresHelperFor(cal);
Boolean removeSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
String uid = kalendarEvent.getID();
String recurrenceId = kalendarEvent.getRecurrenceID();
Kalendar loadedCal = getCalendarFromCache(cal.getType(), cal.getCalendarID());
if(StringHelper.containsNonWhitespace(recurrenceId)) {
loadedCal.removeEvent(kalendarEvent);
KalendarEvent rootEvent = loadedCal.getEvent(kalendarEvent.getID(), null);
if(rootEvent != null && kalendarEvent instanceof KalendarRecurEvent) {
Date recurrenceDate = ((KalendarRecurEvent)kalendarEvent).getOccurenceDate();
rootEvent.addRecurrenceExc(recurrenceDate);
}
} else {
for(KalendarEvent kEvent:loadedCal.getEvents()) {
if(uid.equals(kEvent.getID())) {
loadedCal.removeEvent(kEvent);
}
}
}
boolean successfullyPersist = persistCalendar(loadedCal);
return new Boolean(successfullyPersist);
}
});
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class));
return removeSuccessful.booleanValue();
}
@Override
public boolean removeOccurenceOfEvent(final Kalendar cal, final KalendarRecurEvent kalendarEvent) {
OLATResourceable calOres = getOresHelperFor(cal);
Boolean removeSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
String uid = kalendarEvent.getID();
Date occurenceDate = kalendarEvent.getBegin();
Kalendar loadedCal = getCalendarFromCache(cal.getType(), cal.getCalendarID());
KalendarEvent rootEvent = loadedCal.getEvent(kalendarEvent.getID(), null);
rootEvent.addRecurrenceExc(kalendarEvent.getBegin());
for(KalendarEvent kEvent:loadedCal.getEvents()) {
if(uid.equals(kEvent.getID())
&& kEvent.getOccurenceDate() != null
&& occurenceDate.equals(kEvent.getOccurenceDate())) {
loadedCal.removeEvent(kEvent);
}
}
boolean successfullyPersist = persistCalendar(loadedCal);
return new Boolean(successfullyPersist);
}
});
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class));
return removeSuccessful.booleanValue();
}
@Override
public boolean removeFutureOfEvent(Kalendar cal, KalendarRecurEvent kalendarEvent) {
OLATResourceable calOres = getOresHelperFor(cal);
Boolean removeSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
boolean successfullyPersist = false;
try {
String uid = kalendarEvent.getID();
Date occurenceDate = kalendarEvent.getOccurenceDate();
Kalendar loadedCal = getCalendarFromCache(cal.getType(), cal.getCalendarID());
KalendarEvent rootEvent = loadedCal.getEvent(kalendarEvent.getID(), null);
String rRule = rootEvent.getRecurrenceRule();
Recur recur = new Recur(rRule);
recur.setUntil(CalendarUtils.createDate(occurenceDate));
RRule rrule = new RRule(recur);
rootEvent.setRecurrenceRule(rrule.getValue());
for(KalendarEvent kEvent:loadedCal.getEvents()) {
if(uid.equals(kEvent.getID())
&& StringHelper.containsNonWhitespace(kEvent.getRecurrenceID())
&& occurenceDate.before(kEvent.getBegin())) {
loadedCal.removeEvent(kEvent);
}
}
successfullyPersist = persistCalendar(loadedCal);
} catch (ParseException e) {
log.error("", e);
}
return new Boolean(successfullyPersist);
}
});
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class));
return removeSuccessful.booleanValue();
}
/**
* @see org.olat.commons.calendar.CalendarManager#updateEventFrom(org.olat.commons.calendar.model.Kalendar, org.olat.commons.calendar.model.KalendarEvent)
*/
@Override
public boolean updateEventFrom(final Kalendar cal, final KalendarEvent kalendarEvent) {
OLATResourceable calOres = getOresHelperFor(cal);
Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
return updateEventAlreadyInSync(cal, kalendarEvent);
}
});
return updatedSuccessful.booleanValue();
}
@Override
public boolean updateEventsFrom(Kalendar cal, List<KalendarEvent> kalendarEvents) {
final OLATResourceable calOres = getOresHelperFor(cal);
Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
Kalendar loadedCal = getCalendarFromCache(cal.getType(), cal.getCalendarID());
for(KalendarEvent kalendarEvent:kalendarEvents) {
loadedCal.removeEvent(kalendarEvent); // remove old event
loadedCal.addEvent(kalendarEvent); // add changed event
}
boolean successfullyPersist = persistCalendar(loadedCal);
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class));
return successfullyPersist;
}
});
return updatedSuccessful.booleanValue();
}
/**
* @see org.olat.commons.calendar.CalendarManager#updateEventFrom(org.olat.commons.calendar.model.Kalendar, org.olat.commons.calendar.model.KalendarEvent)
*/
@Override
public boolean updateEventAlreadyInSync(final Kalendar cal, final KalendarEvent kalendarEvent) {
OLATResourceable calOres = getOresHelperFor(cal);
CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(calOres);
Kalendar reloadedCal = getCalendarFromCache(cal.getType(), cal.getCalendarID());
if(StringHelper.containsNonWhitespace(kalendarEvent.getRecurrenceRule())) {
Date oldBegin = kalendarEvent.getImmutableBegin();
Date oldEnd = kalendarEvent.getImmutableEnd();
KalendarEvent originalEvent = reloadedCal.getEvent(kalendarEvent.getID(), null);
Date newBegin = kalendarEvent.getBegin();
Date newEnd = kalendarEvent.getEnd();
long beginDiff = newBegin.getTime() - oldBegin.getTime();
long endDiff = newEnd.getTime() - oldEnd.getTime();
java.util.Calendar cl = java.util.Calendar.getInstance();
cl.setTime(originalEvent.getBegin());
cl.add(java.util.Calendar.MILLISECOND, (int)beginDiff);
kalendarEvent.setBegin(cl.getTime());
cl.setTime(originalEvent.getEnd());
cl.add(java.util.Calendar.MILLISECOND, (int)endDiff);
kalendarEvent.setEnd(cl.getTime());
List<KalendarEvent> exEvents = new ArrayList<>();
List<KalendarEvent> allEvents = reloadedCal.getEvents();
for(KalendarEvent event:allEvents) {
if(event.getID().equals(kalendarEvent.getID()) && StringHelper.containsNonWhitespace(event.getRecurrenceID())) {
exEvents.add(event);
}
}
if(exEvents.size() > 0) {
for(KalendarEvent exEvent:exEvents) {
try {
reloadedCal.removeEvent(exEvent);
String recurrenceId = exEvent.getRecurrenceID();
RecurrenceId recurId = new RecurrenceId(recurrenceId, tz);
Date currentRecurrence = recurId.getDate();
java.util.Calendar calc = java.util.Calendar.getInstance();
calc.clear();
calc.setTime(currentRecurrence);
if(beginDiff > 0) {
calc.add(java.util.Calendar.MILLISECOND, (int)beginDiff);
}
Date newRecurrenceDate = calc.getTime();
boolean allDay = kalendarEvent.isAllDayEvent();
RecurrenceId newRecurId;
if(allDay) {
newRecurId = new RecurrenceId(tz);
newRecurId.setDate(CalendarUtils.createDate(newRecurrenceDate));
} else {
String startString = CalendarUtils.formatRecurrenceDate(newRecurrenceDate, false);
newRecurId = new RecurrenceId(startString, tz);
}
exEvent.setRecurrenceID(newRecurId.getValue());
reloadedCal.addEvent(exEvent);
} catch (ParseException e) {
log.error("", e);
}
}
}
}
reloadedCal.removeEvent(kalendarEvent); // remove old event
reloadedCal.addEvent(kalendarEvent); // add changed event
boolean successfullyPersist = persistCalendar(reloadedCal);
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class));
return successfullyPersist;
}
@Override
public boolean updateCalendar(final Kalendar cal, final Kalendar importedCal) {
OLATResourceable calOres = getOresHelperFor(cal);
Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() {
@Override
public Boolean execute() {
Map<KalendarEventKey,KalendarEvent> uidToEvent = new HashMap<>();
for(KalendarEvent event:cal.getEvents()) {
if(StringHelper.containsNonWhitespace(event.getID())) {
uidToEvent.put(new KalendarEventKey(event), event);
}
}
Kalendar loadedCal = getCalendarFromCache(cal.getType(), cal.getCalendarID());
for(KalendarEvent importedEvent:importedCal.getEvents()) {
KalendarEventKey uid = new KalendarEventKey(importedEvent);
if(uidToEvent.containsKey(uid)) {
loadedCal.removeEvent(importedEvent); // remove old event
loadedCal.addEvent(importedEvent); // add changed event
} else {
loadedCal.addEvent(importedEvent);
}
}
boolean successfullyPersist = persistCalendar(cal);
// inform all controller about calendar change for reload
CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class));
return new Boolean(successfullyPersist);
}
});
return updatedSuccessful.booleanValue();
}
/**
* Load a calendar when a calendar exists or create a new one.
* This method is not thread-safe. Must be called from synchronized block!
* @param callType
* @param callCalendarID
* @return
*/
protected Kalendar loadOrCreateCalendar(final String callType, final String callCalendarID) {
if (!calendarExists(callType, callCalendarID)) {
return createCalendar(callType, callCalendarID);
} else {
return loadCalendarFromFile(callType, callCalendarID);
}
}
@Override
public List<KalendarEvent> getEvents(Kalendar calendar, Date from, Date to, boolean privateEventsVisible) {
List<KalendarEvent> allEvents = calendar.getEvents();
List<KalendarEvent> events = new ArrayList<>(128);
Map<String, List<KalendarRecurEvent>> idToRecurringEvents = new HashMap<>();
//first pass, ignore events with recurrenceId
for(KalendarEvent event:allEvents) {
if(!privateEventsVisible && event.getClassification() == KalendarEvent.CLASS_PRIVATE) {
continue;
}
if(StringHelper.containsNonWhitespace(event.getRecurrenceID())) {
continue;
}
if (StringHelper.containsNonWhitespace(event.getRecurrenceRule())) {
List<KalendarRecurEvent> recurringEvents = getRecurringEventsInPeriod(event, from, to, tz);
if(recurringEvents.size() > 0) {
idToRecurringEvents.put(event.getID(), recurringEvents);
for (KalendarRecurEvent recurEvent:recurringEvents) {
events.add(recurEvent);
}
}
} else if(isInRange(from, to, event)) {
events.add(event);
}
}
//process events with recurrenceId
for(KalendarEvent event:allEvents) {
if(!StringHelper.containsNonWhitespace(event.getRecurrenceID())) {
continue;
}
String id = event.getID();
if(idToRecurringEvents.containsKey(id)) {
VEvent vEvent = getVEvent(event);
RecurrenceId recurrenceId = vEvent.getRecurrenceId();
net.fortuna.ical4j.model.Date startDate = recurrenceId.getDate();
if(startDate instanceof net.fortuna.ical4j.model.DateTime) {
List<KalendarRecurEvent> recurringEvents = idToRecurringEvents.get(id);
for(KalendarRecurEvent recurEvent:recurringEvents) {
Date beginDate = recurEvent.getBegin();
if(beginDate.equals(startDate)) {
recurEvent.setRecurrenceEvent(event);
}
}
} else {
List<KalendarRecurEvent> recurringEvents = idToRecurringEvents.get(id);
for(KalendarRecurEvent recurEvent:recurringEvents) {
Date beginDate = recurEvent.getBegin();
net.fortuna.ical4j.model.Date occDate = CalendarUtils.createDate(beginDate);
if(occDate.equals(startDate)) {
recurEvent.setRecurrenceEvent(event);
}
}
}
}
}
return events;
}
private final boolean isInRange(Date from, Date to, KalendarEvent event) {
Date end = event.getEnd();
Date begin = event.getBegin();
if(begin != null && end != null) {
if(from.compareTo(begin) <= 0 && to.compareTo(end) >= 0) {
return true;
} else if(begin.compareTo(from) <= 0 && end.compareTo(to) >= 0) {
return true;
} else if(from.compareTo(begin) <= 0 && to.compareTo(begin) >= 0) {
return true;
} else if(from.compareTo(end) <= 0 && to.compareTo(end) >= 0) {
return true;
}
} else if(begin != null) {
if(from.compareTo(begin) <= 0 && to.compareTo(begin) >= 0) {
return false;
}
} else if(end != null) {
if(from.compareTo(end) <= 0 && to.compareTo(end) >= 0) {
return true;
}
}
return false;
}
private final List<KalendarRecurEvent> getRecurringEventsInPeriod(KalendarEvent kEvent, Date periodStart, Date periodEnd, TimeZone userTz) {
VEvent vEvent = getVEvent(kEvent);
//calculate the events in the specified period
Period recurringPeriod = new Period(new DateTime(periodStart), new DateTime(periodEnd));
PeriodList periodList = vEvent.calculateRecurrenceSet(recurringPeriod);
List<KalendarRecurEvent> recurringEvents = new ArrayList<>(periodList.size());
for(Object obj : periodList) {
Period period = (Period)obj;
Date date = period.getStart();
java.util.Calendar eventStartCal = java.util.Calendar.getInstance();
eventStartCal.clear();
eventStartCal.setTime(kEvent.getBegin());
java.util.Calendar eventEndCal = java.util.Calendar.getInstance();
eventEndCal.clear();
eventEndCal.setTime(kEvent.getEnd());
java.util.Calendar recurStartCal = java.util.Calendar.getInstance();
recurStartCal.clear();
if(userTz == null) {
recurStartCal.setTimeInMillis(date.getTime());
} else {
recurStartCal.setTimeInMillis(date.getTime() - userTz.getOffset(date.getTime()));
}
long duration = kEvent.getEnd().getTime() - kEvent.getBegin().getTime();
java.util.Calendar beginCal = java.util.Calendar.getInstance();
beginCal.clear();
beginCal.set(recurStartCal.get(java.util.Calendar.YEAR), recurStartCal.get(java.util.Calendar.MONTH), recurStartCal.get(java.util.Calendar.DATE),
eventStartCal.get(java.util.Calendar.HOUR_OF_DAY), eventStartCal.get(java.util.Calendar.MINUTE), eventStartCal.get(java.util.Calendar.SECOND));
java.util.Calendar endCal = java.util.Calendar.getInstance();
endCal.clear();
endCal.setTimeInMillis(beginCal.getTimeInMillis() + duration);
boolean original = false;
if(kEvent.getBegin().compareTo(beginCal.getTime()) == 0) {
original = true; //prevent doubled events
}
Date recurrenceEnd = getRecurrenceEndDate(kEvent.getRecurrenceRule());
if(kEvent.isAllDayEvent() && recurrenceEnd != null && recurStartCal.getTime().after(recurrenceEnd)) {
continue; //workaround for ical4j-bug in all day events
}
KalendarRecurEvent recurEvent = new KalendarRecurEvent(kEvent.getID(), original, kEvent.getSubject(), beginCal.getTime(), endCal.getTime());
recurEvent.setOccurenceDate(beginCal.getTime());
recurEvent.setSourceEvent(kEvent);
recurringEvents.add(recurEvent);
}
return recurringEvents;
}
private final DateList getRecurringsInPeriod(Date periodStart, Date periodEnd, KalendarEvent kEvent) {
DateList recurDates = null;
String recurrenceRule = kEvent.getRecurrenceRule();
if(StringHelper.containsNonWhitespace(recurrenceRule)) {
try {
Recur recur = new Recur(recurrenceRule);
net.fortuna.ical4j.model.Date periodStartDate = CalendarUtils.createDate(periodStart);
net.fortuna.ical4j.model.Date periodEndDate = CalendarUtils.createDate(periodEnd);
net.fortuna.ical4j.model.Date eventStartDate = CalendarUtils.createDate(kEvent.getBegin());
recurDates = recur.getDates(eventStartDate, periodStartDate, periodEndDate, Value.DATE);
} catch (ParseException e) {
log.error("cannot restore recurrence rule: " + recurrenceRule, e);
}
String recurrenceExc = kEvent.getRecurrenceExc();
if(recurrenceExc != null && !recurrenceExc.equals("")) {
try {
ExDate exdate = new ExDate();
// expected date+time format:
// 20100730T100000
// unexpected all-day format:
// 20100730
// see OLAT-5645
if (recurrenceExc.length() > 8) {
exdate.setValue(recurrenceExc);
} else {
exdate.getParameters().replace(Value.DATE);
exdate.setValue(recurrenceExc);
}
for( Object date : exdate.getDates() ) {
if(recurDates.contains(date)) recurDates.remove(date);
}
} catch (ParseException e) {
log.error("cannot restore excluded dates for this recurrence: " + recurrenceExc, e);
}
}
}
return recurDates;
}
/**
*
* @param rule
* @return date of recurrence end
*/
@Override
public Date getRecurrenceEndDate(String rule) {
if (rule != null) {
try {
TimeZone ltz = calendarModule.getDefaultTimeZone();
Recur recur = new Recur(rule);
Date dUntil = recur.getUntil();
DateTime dtUntil = dUntil == null ? null : new DateTime(dUntil.getTime());
if(dtUntil != null) {
if(ltz != null) {
dtUntil.setTimeZone(ltz);
}
return dtUntil;
}
} catch (ParseException e) {
log.error("cannot restore recurrence rule", e);
}
}
return null;
}
/**
* Build iCalendar-compliant recurrence rule
* @param recurrence
* @param recurrenceEnd
* @return rrule
*/
@Override
public String getRecurrenceRule(String recurrence, Date recurrenceEnd) {
if (recurrence != null) { // recurrence available
// create recurrence rule
StringBuilder sb = new StringBuilder();
sb.append("FREQ=");
if(recurrence.equals(KalendarEvent.WORKDAILY)) {
// build rule for monday to friday
sb.append(KalendarEvent.DAILY).append(";").append("BYDAY=MO,TU,WE,TH,FR");
} else if(recurrence.equals(KalendarEvent.BIWEEKLY)) {
// build rule for biweekly
sb.append(KalendarEvent.WEEKLY).append(";").append("INTERVAL=2");
} else {
// normal supported recurrence
sb.append(recurrence);
}
if(recurrenceEnd != null) {
java.util.Calendar recurEndCal = java.util.Calendar.getInstance();
recurEndCal.setTimeZone(tz);
recurEndCal.setTime(recurrenceEnd);
recurEndCal = CalendarUtils.getEndOfDay(recurEndCal);
long recTime = recurEndCal.getTimeInMillis() - tz.getOffset(recurEndCal.getTimeInMillis());
DateTime recurEndDT = new DateTime(recTime);
if(tz != null) {
recurEndDT.setTimeZone(tz);
}
sb.append(";").append(KalendarEvent.UNTIL).append("=").append(recurEndDT.toString());
}
try {
Recur recur = new Recur(sb.toString());
RRule rrule = new RRule(recur);
return rrule.getValue();
} catch (ParseException e) {
log.error("cannot create recurrence rule: " + recurrence.toString(), e);
}
}
return null;
}
}