Java tutorial
/* * Copyright (C) 2014 Sonicle S.r.l. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY SONICLE, SONICLE DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Sonicle S.r.l. at email address sonicle[at]sonicle[dot]com * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * Sonicle logo and Sonicle copyright notice. If the display of the logo is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Copyright (C) 2014 Sonicle S.r.l.". */ package com.sonicle.webtop.calendar; import com.github.rutledgepaulv.qbuilders.conditions.Condition; import com.sonicle.webtop.core.app.util.EmailNotification; import com.sonicle.webtop.calendar.model.GetEventScope; import com.rits.cloning.Cloner; import com.sonicle.webtop.core.util.ICal4jUtils; import com.sonicle.commons.EnumUtils; import com.sonicle.commons.IdentifierUtils; import com.sonicle.commons.InternetAddressUtils; import com.sonicle.commons.http.HttpClientUtils; import com.sonicle.commons.LangUtils; import com.sonicle.commons.LangUtils.CollectionChangeSet; import com.sonicle.commons.PathUtils; import com.sonicle.commons.URIUtils; import com.sonicle.commons.db.DbUtils; import com.sonicle.commons.time.DateRange; import com.sonicle.commons.time.DateTimeRange; import com.sonicle.commons.time.DateTimeUtils; import com.sonicle.commons.web.Crud; import com.sonicle.commons.web.json.CompositeId; import com.sonicle.dav.CalDav; import com.sonicle.dav.CalDavFactory; import com.sonicle.dav.DavSyncStatus; import com.sonicle.dav.DavUtil; import com.sonicle.dav.caldav.DavCalendar; import com.sonicle.dav.caldav.DavCalendarEvent; import com.sonicle.dav.impl.DavException; import com.sonicle.webtop.calendar.model.Event; import com.sonicle.webtop.calendar.bol.VVEvent; import com.sonicle.webtop.calendar.bol.VVEventInstance; import com.sonicle.webtop.calendar.bol.OCalendar; import com.sonicle.webtop.calendar.bol.OCalendarOwnerInfo; import com.sonicle.webtop.calendar.bol.OCalendarPropSet; import com.sonicle.webtop.calendar.bol.OEvent; import com.sonicle.webtop.calendar.bol.OEventAttachment; import com.sonicle.webtop.calendar.bol.OEventAttachmentData; import com.sonicle.webtop.calendar.bol.OEventAttendee; import com.sonicle.webtop.calendar.bol.OEventICalendar; import com.sonicle.webtop.calendar.bol.OEventInfo; import com.sonicle.webtop.calendar.bol.ORecurrence; import com.sonicle.webtop.calendar.bol.ORecurrenceBroken; import com.sonicle.webtop.calendar.bol.VEventObject; import com.sonicle.webtop.calendar.bol.VEventObjectChanged; import com.sonicle.webtop.calendar.bol.VEventHrefSync; import com.sonicle.webtop.calendar.bol.VExpEvent; import com.sonicle.webtop.calendar.bol.VExpEventInstance; import com.sonicle.webtop.calendar.bol.model.MyShareRootCalendar; import com.sonicle.webtop.calendar.model.ShareFolderCalendar; import com.sonicle.webtop.calendar.model.ShareRootCalendar; import com.sonicle.webtop.calendar.model.EventAttendee; import com.sonicle.webtop.calendar.model.EventInstance; import com.sonicle.webtop.calendar.model.EventKey; import com.sonicle.webtop.calendar.dal.CalendarDAO; import com.sonicle.webtop.calendar.dal.CalendarPropsDAO; import com.sonicle.webtop.calendar.dal.EventAttachmentDAO; import com.sonicle.webtop.calendar.dal.EventAttendeeDAO; import com.sonicle.webtop.calendar.dal.EventDAO; import com.sonicle.webtop.calendar.dal.EventICalendarDAO; import com.sonicle.webtop.calendar.dal.EventPredicateVisitor; import com.sonicle.webtop.calendar.dal.RecurrenceBrokenDAO; import com.sonicle.webtop.calendar.dal.RecurrenceDAO; import com.sonicle.webtop.calendar.io.EventInput; import com.sonicle.webtop.core.CoreManager; import com.sonicle.webtop.core.CoreUserSettings; import com.sonicle.webtop.core.app.RunContext; import com.sonicle.webtop.core.app.WT; import com.sonicle.webtop.core.bol.OActivity; import com.sonicle.webtop.core.bol.OCausal; import com.sonicle.webtop.core.bol.OShare; import com.sonicle.webtop.core.bol.OUser; import com.sonicle.webtop.core.dal.ActivityDAO; import com.sonicle.webtop.core.dal.CausalDAO; import com.sonicle.webtop.core.dal.UserDAO; import com.sonicle.webtop.core.sdk.BaseManager; import com.sonicle.webtop.core.model.IncomingShareRoot; import com.sonicle.webtop.core.bol.model.Sharing; import com.sonicle.webtop.core.model.SharePermsFolder; import com.sonicle.webtop.core.model.SharePermsElements; import com.sonicle.webtop.core.model.SharePermsRoot; import com.sonicle.webtop.core.dal.DAOException; import com.sonicle.webtop.core.sdk.AuthException; import com.sonicle.webtop.core.sdk.BaseReminder; import com.sonicle.webtop.core.sdk.ReminderEmail; import com.sonicle.webtop.core.sdk.ReminderInApp; import com.sonicle.webtop.core.sdk.UserProfile; import com.sonicle.webtop.core.sdk.WTException; import com.sonicle.webtop.core.sdk.WTRuntimeException; import com.sonicle.webtop.core.util.LogEntries; import com.sonicle.webtop.core.util.LogEntry; import com.sonicle.webtop.core.util.MessageLogEntry; import com.sonicle.webtop.core.util.NotificationHelper; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.sql.Connection; import java.sql.SQLException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import net.fortuna.ical4j.model.PeriodList; import net.fortuna.ical4j.model.property.RRule; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Days; import org.joda.time.LocalDate; import org.joda.time.LocalTime; import org.joda.time.Minutes; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.supercsv.cellprocessor.constraint.NotNull; import org.supercsv.cellprocessor.ift.CellProcessor; import org.supercsv.cellprocessor.joda.FmtDateTime; import org.supercsv.io.CsvMapWriter; import org.supercsv.io.ICsvMapWriter; import org.supercsv.prefs.CsvPreference; import com.sonicle.webtop.calendar.io.EventFileReader; import com.sonicle.webtop.calendar.io.ICalendarOutput; import com.sonicle.webtop.calendar.model.Calendar; import com.sonicle.webtop.calendar.model.CalendarPropSet; import com.sonicle.webtop.calendar.model.CalendarRemoteParameters; import com.sonicle.webtop.calendar.model.EventObject; import com.sonicle.webtop.calendar.model.EventObjectChanged; import com.sonicle.webtop.calendar.model.EventFootprint; import com.sonicle.webtop.calendar.model.FolderEventInstances; import com.sonicle.webtop.calendar.model.UpdateEventTarget; import com.sonicle.webtop.calendar.model.SchedEventInstance; import com.sonicle.webtop.calendar.io.ICalendarInput; import com.sonicle.webtop.calendar.model.EventAttachment; import com.sonicle.webtop.calendar.model.EventAttachmentWithBytes; import com.sonicle.webtop.calendar.model.EventAttachmentWithStream; import com.sonicle.webtop.calendar.model.EventObjectWithBean; import com.sonicle.webtop.calendar.model.EventObjectWithICalendar; import com.sonicle.webtop.calendar.model.EventQuery; import com.sonicle.webtop.core.dal.BaseDAO; import com.sonicle.webtop.core.dal.DAOIntegrityViolationException; import com.sonicle.webtop.core.model.MasterData; import com.sonicle.webtop.core.sdk.AbstractMapCache; import com.sonicle.webtop.core.sdk.AbstractShareCache; import com.sonicle.webtop.core.sdk.UserProfileId; import com.sonicle.webtop.core.util.ICalendarUtils; import com.sonicle.webtop.mail.IMailManager; import freemarker.template.TemplateException; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URI; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import javax.mail.Session; import javax.mail.internet.AddressException; import javax.mail.internet.MimeMultipart; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.DateList; import net.fortuna.ical4j.model.Parameter; import net.fortuna.ical4j.model.Recur; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.parameter.PartStat; import net.fortuna.ical4j.model.property.Attendee; import net.fortuna.ical4j.model.property.Method; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.http.client.HttpClient; import org.apache.http.client.utils.URIBuilder; import org.joda.time.Duration; /** * * @author malbinola */ public class CalendarManager extends BaseManager implements ICalendarManager { public static final Logger logger = WT.getLogger(CalendarManager.class); private static final String GROUPNAME_CALENDAR = "CALENDAR"; public static final String TARGET_THIS = "this"; public static final String TARGET_SINCE = "since"; public static final String TARGET_ALL = "all"; public static final String SUGGESTION_EVENT_TITLE = "eventtitle"; public static final String SUGGESTION_EVENT_LOCATION = "eventlocation"; private final OwnerCache ownerCache = new OwnerCache(); private final ShareCache shareCache = new ShareCache(); private static final ConcurrentHashMap<String, UserProfileId> pendingRemoteCalendarSyncs = new ConcurrentHashMap<>(); public CalendarManager(boolean fastInit, UserProfileId targetProfileId) { super(fastInit, targetProfileId); if (!fastInit) { shareCache.init(); } } private CalendarServiceSettings getServiceSettings() { return new CalendarServiceSettings(SERVICE_ID, getTargetProfileId().getDomainId()); } private CalDav getCalDav(String username, String password) { if (!StringUtils.isBlank(username) && !StringUtils.isBlank(username)) { return CalDavFactory.begin(username, password); } else { return CalDavFactory.begin(); } } private List<ShareRootCalendar> internalListIncomingCalendarShareRoots() throws WTException { CoreManager coreMgr = WT.getCoreManager(getTargetProfileId()); List<ShareRootCalendar> roots = new ArrayList(); HashSet<String> hs = new HashSet<>(); for (IncomingShareRoot share : coreMgr.listIncomingShareRoots(SERVICE_ID, GROUPNAME_CALENDAR)) { final SharePermsRoot perms = coreMgr.getShareRootPermissions(share.getShareId()); ShareRootCalendar root = new ShareRootCalendar(share, perms); if (hs.contains(root.getShareId())) continue; // Avoid duplicates ?????????????????????? hs.add(root.getShareId()); roots.add(root); } return roots; } public String buildSharingId(int calendarId) throws WTException { UserProfileId targetPid = getTargetProfileId(); // Skip rights check if running user is resource's owner UserProfileId owner = ownerCache.get(calendarId); if (owner == null) throw new WTException("owner({0}) -> null", calendarId); String rootShareId = null; if (owner.equals(targetPid)) { rootShareId = MyShareRootCalendar.SHARE_ID; } else { rootShareId = shareCache.getShareRootIdByFolderId(calendarId); } if (rootShareId == null) throw new WTException("Unable to find a root share [{0}]", calendarId); return new CompositeId().setTokens(rootShareId, calendarId).toString(); } public Sharing getSharing(String shareId) throws WTException { CoreManager coreMgr = WT.getCoreManager(getTargetProfileId()); return coreMgr.getSharing(SERVICE_ID, GROUPNAME_CALENDAR, shareId); } public void updateSharing(Sharing sharing) throws WTException { CoreManager coreMgr = WT.getCoreManager(getTargetProfileId()); coreMgr.updateSharing(SERVICE_ID, GROUPNAME_CALENDAR, sharing); } @Override public List<ShareRootCalendar> listIncomingCalendarRoots() throws WTException { return shareCache.getShareRoots(); } @Override public Map<Integer, ShareFolderCalendar> listIncomingCalendarFolders(String rootShareId) throws WTException { CoreManager coreMgr = WT.getCoreManager(getTargetProfileId()); LinkedHashMap<Integer, ShareFolderCalendar> folders = new LinkedHashMap<>(); for (Integer folderId : shareCache.getFolderIdsByShareRoot(rootShareId)) { final String shareFolderId = shareCache.getShareFolderIdByFolderId(folderId); if (StringUtils.isBlank(shareFolderId)) continue; SharePermsFolder fperms = coreMgr.getShareFolderPermissions(shareFolderId); SharePermsElements eperms = coreMgr.getShareElementsPermissions(shareFolderId); if (folders.containsKey(folderId)) { final ShareFolderCalendar shareFolder = folders.get(folderId); if (shareFolder == null) continue; shareFolder.getPerms().merge(fperms); shareFolder.getElementsPerms().merge(eperms); } else { final Calendar calendar = getCalendar(folderId); if (calendar == null) continue; folders.put(folderId, new ShareFolderCalendar(shareFolderId, fperms, eperms, calendar)); } } return folders; } @Override public List<Integer> listCalendarIds() throws WTException { return new ArrayList<>(listCalendars().keySet()); } @Override public List<Integer> listIncomingCalendarIds() throws WTException { return shareCache.getFolderIds(); } @Override public Map<Integer, Calendar> listCalendars() throws WTException { return listCalendars(getTargetProfileId()); } private Map<Integer, Calendar> listCalendars(UserProfileId pid) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); LinkedHashMap<Integer, Calendar> items = new LinkedHashMap<>(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); for (OCalendar ocal : calDao.selectByProfile(con, pid.getDomainId(), pid.getUserId())) { //checkRightsOnCalendarFolder(ocal.getCalendarId(), "READ"); items.put(ocal.getCalendarId(), ManagerUtils.createCalendar(ocal)); } return items; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public List<Calendar> listRemoteCalendarsToBeSynchronized() throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); ArrayList<Calendar> items = new ArrayList<>(); Connection con = null; try { ensureSysAdmin(); con = WT.getConnection(SERVICE_ID); for (OCalendar ocal : calDao.selectByProvider(con, Arrays.asList(Calendar.Provider.WEBCAL, Calendar.Provider.CALDAV))) { items.add(ManagerUtils.createCalendar(ocal)); } return items; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Map<Integer, DateTime> getCalendarsLastRevision(Collection<Integer> calendarIds) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { List<Integer> okCalendarIds = calendarIds.stream() .filter(categoryId -> quietlyCheckRightsOnCalendarFolder(categoryId, "READ")) .collect(Collectors.toList()); con = WT.getConnection(SERVICE_ID); return evtDao.selectMaxRevTimestampByCalendars(con, okCalendarIds); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public UserProfileId getCalendarOwner(int calendarId) throws WTException { return ownerCache.get(calendarId); } public String getIncomingCalendarShareRootId(int calendarId) throws WTException { return shareCache.getShareRootIdByFolderId(calendarId); } @Override public boolean existCalendar(int calendarId) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); boolean ret = calDao.existsById(con, calendarId); if (ret) checkRightsOnCalendarFolder(calendarId, "READ"); return ret; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Calendar getCalendar(int calendarId) throws WTException { Connection con = null; try { checkRightsOnCalendarFolder(calendarId, "READ"); con = WT.getConnection(SERVICE_ID); return doCalendarGet(con, calendarId); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Calendar getBuiltInCalendar() throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); OCalendar ocal = calDao.selectBuiltInByProfile(con, getTargetProfileId().getDomainId(), getTargetProfileId().getUserId()); if (ocal == null) return null; checkRightsOnCalendarFolder(ocal.getCalendarId(), "READ"); return ManagerUtils.createCalendar(ocal); } catch (SQLException | DAOException | WTException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public Map<String, String> getCalendarLinks(int calendarId) throws WTException { checkRightsOnCalendarFolder(calendarId, "READ"); UserProfile.Data ud = WT.getUserData(getTargetProfileId()); String davServerBaseUrl = WT.getDavServerBaseUrl(getTargetProfileId().getDomainId()); String calendarUid = ManagerUtils.encodeAsCalendarUid(calendarId); String calendarUrl = MessageFormat.format(ManagerUtils.CALDAV_CALENDAR_URL, ud.getProfileEmailAddress(), calendarUid); LinkedHashMap<String, String> links = new LinkedHashMap<>(); links.put(ManagerUtils.CALENDAR_LINK_CALDAV, PathUtils.concatPathParts(davServerBaseUrl, calendarUrl)); return links; } @Override public Calendar addCalendar(Calendar calendar) throws WTException { Connection con = null; try { checkRightsOnCalendarRoot(calendar.getProfileId(), "MANAGE"); con = WT.getConnection(SERVICE_ID, false); calendar.setBuiltIn(false); calendar = doCalendarInsert(con, calendar); DbUtils.commitQuietly(con); writeLog("CALENDAR_INSERT", String.valueOf(calendar.getCalendarId())); return calendar; } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Calendar addBuiltInCalendar() throws WTException { CalendarDAO caldao = CalendarDAO.getInstance(); Connection con = null; try { checkRightsOnCalendarRoot(getTargetProfileId(), "MANAGE"); con = WT.getConnection(SERVICE_ID, false); OCalendar ocal = caldao.selectBuiltInByProfile(con, getTargetProfileId().getDomainId(), getTargetProfileId().getUserId()); if (ocal != null) { logger.debug("Built-in calendar already present"); return null; } Calendar cal = new Calendar(); cal.setBuiltIn(true); cal.setName(WT.getPlatformName()); cal.setDescription(""); cal.setIsDefault(true); cal = doCalendarInsert(con, cal); DbUtils.commitQuietly(con); writeLog("CALENDAR_INSERT", String.valueOf(cal.getCalendarId())); return cal; } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public void updateCalendar(Calendar calendar) throws WTException { Connection con = null; try { int calendarId = calendar.getCalendarId(); checkRightsOnCalendarFolder(calendarId, "UPDATE"); con = WT.getConnection(SERVICE_ID, false); boolean updated = doCalendarUpdate(con, calendar); if (!updated) throw new NotFoundException("Calendar not found [{}]", calendarId); DbUtils.commitQuietly(con); writeLog("CALENDAR_UPDATE", String.valueOf(calendarId)); } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public void deleteCalendar(int calendarId) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); CalendarPropsDAO psetDao = CalendarPropsDAO.getInstance(); Connection con = null; try { checkRightsOnCalendarFolder(calendarId, "DELETE"); // Retrieve sharing status (for later) String sharingId = buildSharingId(calendarId); Sharing sharing = getSharing(sharingId); con = WT.getConnection(SERVICE_ID, false); Calendar cal = ManagerUtils.createCalendar(calDao.selectById(con, calendarId)); if (cal == null) throw new NotFoundException("Calendar not found [{}]", calendarId); int deleted = calDao.deleteById(con, calendarId); psetDao.deleteByCalendar(con, calendarId); doEventsDeleteByCalendar(con, calendarId, !cal.isProviderRemote()); // Cleanup sharing, if necessary if ((sharing != null) && !sharing.getRights().isEmpty()) { logger.debug("Removing {} active sharing [{}]", sharing.getRights().size(), sharing.getId()); sharing.getRights().clear(); updateSharing(sharing); } DbUtils.commitQuietly(con); final String ref = String.valueOf(calendarId); writeLog("CALENDAR_DELETE", ref); writeLog("EVENT_DELETE", "*@" + ref); } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public CalendarPropSet getCalendarCustomProps(int calendarId) throws WTException { return getCalendarCustomProps(getTargetProfileId(), calendarId); } private CalendarPropSet getCalendarCustomProps(UserProfileId profileId, int calendarId) throws WTException { CalendarPropsDAO psetDao = CalendarPropsDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); OCalendarPropSet opset = psetDao.selectByProfileCalendar(con, profileId.getDomainId(), profileId.getUserId(), calendarId); return (opset == null) ? new CalendarPropSet() : ManagerUtils.createCalendarPropSet(opset); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Map<Integer, CalendarPropSet> getCalendarCustomProps(Collection<Integer> calendarIds) throws WTException { return getCalendarCustomProps(getTargetProfileId(), calendarIds); } public Map<Integer, CalendarPropSet> getCalendarCustomProps(UserProfileId profileId, Collection<Integer> calendarIds) throws WTException { CalendarPropsDAO psetDao = CalendarPropsDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); LinkedHashMap<Integer, CalendarPropSet> psets = new LinkedHashMap<>(calendarIds.size()); Map<Integer, OCalendarPropSet> map = psetDao.selectByProfileCalendarIn(con, profileId.getDomainId(), profileId.getUserId(), calendarIds); for (Integer categoryId : calendarIds) { OCalendarPropSet opset = map.get(categoryId); psets.put(categoryId, (opset == null) ? new CalendarPropSet() : ManagerUtils.createCalendarPropSet(opset)); } return psets; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public CalendarPropSet updateCalendarCustomProps(int calendarId, CalendarPropSet propertySet) throws WTException { ensureUser(); return updateCalendarCustomProps(getTargetProfileId(), calendarId, propertySet); } private CalendarPropSet updateCalendarCustomProps(UserProfileId profileId, int calendarId, CalendarPropSet propertySet) throws WTException { CalendarPropsDAO psetDao = CalendarPropsDAO.getInstance(); Connection con = null; try { OCalendarPropSet opset = ManagerUtils.createOCalendarPropSet(propertySet); opset.setDomainId(profileId.getDomainId()); opset.setUserId(profileId.getUserId()); opset.setCalendarId(calendarId); con = WT.getConnection(SERVICE_ID); try { psetDao.insert(con, opset); } catch (DAOIntegrityViolationException ex1) { psetDao.update(con, opset); } return propertySet; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public List<LocalDate> listEventDates(Collection<Integer> calendarIds, DateTime from, DateTime to, DateTimeZone refTimezone) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); List<Integer> okCalendarIds = calendarIds.stream() .filter(calendarId -> quietlyCheckRightsOnCalendarFolder(calendarId, "READ")) .collect(Collectors.toList()); HashSet<LocalDate> dates = new HashSet<>(); for (VVEvent vevt : evtDao.viewByCalendarRangeCondition(con, okCalendarIds, from, to, null)) { dates.addAll(CalendarUtils.getDatesSpan(vevt.getAllDay(), vevt.getStartDate(), vevt.getEndDate(), DateTimeZone.forID(vevt.getTimezone()))); } int noOfRecurringInst = Days.daysBetween(from, to).getDays() + 2; for (VVEvent vevt : evtDao.viewRecurringByCalendarRangeCondition(con, okCalendarIds, from, to, null)) { final List<SchedEventInstance> instances = calculateRecurringInstances(con, new SchedEventInstanceMapper(vevt), from, to, refTimezone, noOfRecurringInst); for (SchedEventInstance instance : instances) { dates.addAll(CalendarUtils.getDatesSpan(instance.getAllDay(), instance.getStartDate(), instance.getEndDate(), instance.getDateTimeZone())); } } return new ArrayList<>(dates); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public List<EventObject> listEventObjects(int calendarId, EventObjectOutputType outputType) throws WTException { return CalendarManager.this.listEventObjects(calendarId, null, outputType); } @Override public List<EventObject> listEventObjects(int calendarId, DateTime since, EventObjectOutputType outputType) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); checkRightsOnCalendarFolder(calendarId, "READ"); ArrayList<EventObject> items = new ArrayList<>(); Map<String, List<VEventObject>> vobjMap = null; if (since == null) { vobjMap = evtDao.viewCalObjectsByCalendar(con, calendarId); } else { vobjMap = evtDao.viewCalObjectsByCalendarSince(con, calendarId, since); } for (List<VEventObject> vobjs : vobjMap.values()) { if (vobjs.isEmpty()) continue; VEventObject vobj = vobjs.get(vobjs.size() - 1); if (vobjs.size() > 1) { logger.trace("Many events ({}) found for same href [{} -> {}]", vobjs.size(), vobj.getHref(), vobj.getEventId()); } items.add(doEventObjectPrepare(con, vobj, outputType)); } return items; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public CollectionChangeSet<EventObjectChanged> listEventObjectsChanges(int calendarId, DateTime since, Integer limit) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); checkRightsOnCalendarFolder(calendarId, "READ"); ArrayList<EventObjectChanged> inserted = new ArrayList<>(); ArrayList<EventObjectChanged> updated = new ArrayList<>(); ArrayList<EventObjectChanged> deleted = new ArrayList<>(); if (limit == null) limit = Integer.MAX_VALUE; if (since == null) { List<VEventObjectChanged> vevts = evtDao.viewChangedLiveCalObjectsByCalendar(con, calendarId, limit); for (VEventObjectChanged vevt : vevts) { inserted.add( new EventObjectChanged(vevt.getEventId(), vevt.getRevisionTimestamp(), vevt.getHref())); } } else { List<VEventObjectChanged> vevts = evtDao.viewChangedCalObjectsByCalendarSince(con, calendarId, since, limit); for (VEventObjectChanged vevt : vevts) { Event.RevisionStatus revStatus = EnumUtils.forSerializedName(vevt.getRevisionStatus(), Event.RevisionStatus.class); if (Event.RevisionStatus.DELETED.equals(revStatus)) { deleted.add(new EventObjectChanged(vevt.getEventId(), vevt.getRevisionTimestamp(), vevt.getHref())); } else { if (Event.RevisionStatus.NEW.equals(revStatus) || (vevt.getCreationTimestamp().compareTo(since) >= 0)) { inserted.add(new EventObjectChanged(vevt.getEventId(), vevt.getRevisionTimestamp(), vevt.getHref())); } else { updated.add(new EventObjectChanged(vevt.getEventId(), vevt.getRevisionTimestamp(), vevt.getHref())); } } } } return new CollectionChangeSet<>(inserted, updated, deleted); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public EventObjectWithICalendar getEventObjectWithICalendar(int calendarId, String href) throws WTException { List<EventObjectWithICalendar> ccs = getEventObjectsWithICalendar(calendarId, Arrays.asList(href)); return ccs.isEmpty() ? null : ccs.get(0); } @Override public List<EventObjectWithICalendar> getEventObjectsWithICalendar(int calendarId, Collection<String> hrefs) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); checkRightsOnCalendarFolder(calendarId, "READ"); ArrayList<EventObjectWithICalendar> items = new ArrayList<>(); Map<String, List<VEventObject>> map = evtDao.viewCalObjectsByCalendarHrefs(con, calendarId, hrefs); for (String href : hrefs) { List<VEventObject> vevts = map.get(href); if (vevts == null) continue; if (vevts.isEmpty()) continue; VEventObject vevt = vevts.get(vevts.size() - 1); if (vevts.size() > 1) { logger.trace("Many events ({}) found for same href [{} -> {}]", vevts.size(), vevt.getHref(), vevt.getEventId()); } items.add((EventObjectWithICalendar) doEventObjectPrepare(con, vevt, EventObjectOutputType.ICALENDAR)); } return items; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public EventObject getEventObject(int calendarId, int eventId, EventObjectOutputType outputType) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); checkRightsOnCalendarFolder(calendarId, "READ"); VEventObject vevt = evtDao.viewCalObjectById(con, calendarId, eventId); return (vevt == null) ? null : doEventObjectPrepare(con, vevt, outputType); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public void addEventObject(int calendarId, String href, net.fortuna.ical4j.model.Calendar iCalendar) throws WTException { final UserProfile.Data udata = WT.getUserData(getTargetProfileId()); ICalendarInput in = new ICalendarInput(udata.getTimeZone()).withDefaultAttendeeNotify(true); ArrayList<EventInput> eis = in.fromICalendarFile(iCalendar, null); if (eis.isEmpty()) throw new WTException("iCalendar object does not contain any events"); if (eis.size() > 1) throw new WTException("iCalendar object should contain one event"); EventInput ei = eis.get(0); ei.event.setCalendarId(calendarId); ei.event.setHref(href); String rawData = null; if (iCalendar != null) { String prodId = ICalendarUtils.buildProdId(ManagerUtils.getProductName()); try { rawData = new ICalendarOutput(prodId).write(iCalendar); } catch (IOException ex) { throw new WTException(ex, "Error serializing iCalendar"); } } addEvent(ei.event, rawData, true); } @Override public void updateEventObject(int calendarId, String href, net.fortuna.ical4j.model.Calendar iCalendar) throws WTException { final UserProfile.Data udata = WT.getUserData(getTargetProfileId()); int eventId = getEventIdByCategoryHref(calendarId, href, true); ICalendarInput in = new ICalendarInput(udata.getTimeZone()).withDefaultAttendeeNotify(true) .withIncludeVEventSourceInOutput(true); ArrayList<EventInput> eis = in.fromICalendarFile(iCalendar, null); if (eis.isEmpty()) throw new WTException("iCalendar object does not contain any events"); EventInput refInput = eis.remove(0); if (refInput.exRefersToPublicUid != null) throw new WTException("First iCalendar event should not have a RECURRENCE-ID set"); // Collect dates exceptions and prepare broken event to be inserted ArrayList<EventInput> eiExs = new ArrayList<>(); LinkedHashSet<LocalDate> exDates = new LinkedHashSet<>(); for (EventInput ei : eis) { if (!StringUtils.equals(ei.exRefersToPublicUid, refInput.event.getPublicUid())) continue; if (exDates.contains(ei.addsExOnMaster)) continue; exDates.add(ei.addsExOnMaster); if (!ei.isSourceEventCancelled()) eiExs.add(ei); } // Adds new collected exceptions and then updates master event if (!exDates.isEmpty()) { if (refInput.event.hasExcludedDates()) { refInput.event.getExcludedDates().addAll(exDates); } else { refInput.event.setExcludedDates(exDates); } } refInput.event.setEventId(eventId); refInput.event.setCalendarId(calendarId); refInput.event.setExcludedDates(exDates); updateEvent(refInput.event, true); // Here we do not support creating broken events related to the // main event series (re-attach unavailable). Instance exceptions // should have been created above as date exception into the main // event; so simply create exceptions as new events. if (!eiExs.isEmpty()) { for (EventInput eiEx : eiExs) { eiEx.event.setCalendarId(calendarId); eiEx.event.setPublicUid(null); // reset uid //TODO: handle raw value to persist custom properties try { addEvent(eiEx.event, null, true); } catch (Throwable t) { logger.error("Unable to insert exception on {} as new event", eiEx.addsExOnMaster, t); } } } } @Override public void deleteEventObject(int calendarId, String href) throws WTException { int eventId = getEventIdByCategoryHref(calendarId, href, true); deleteEvent(eventId, true); } private int getEventIdByCategoryHref(int calendarId, String href, boolean throwExIfManyMatchesFound) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); List<Integer> ids = evtDao.selectAliveIdsByCalendarHrefs(con, calendarId, href); if (ids.isEmpty()) throw new NotFoundException("Event not found [{}, {}]", calendarId, href); if (throwExIfManyMatchesFound && (ids.size() > 1)) throw new WTException("Many matches for href [{}]", href); return ids.get(ids.size() - 1); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public boolean existEventInstance(Collection<Integer> calendarIds, Condition<EventQuery> conditionPredicate, DateTimeZone targetTimezone) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { List<Integer> okCalendarIds = calendarIds.stream() .filter(calendarId -> quietlyCheckRightsOnCalendarFolder(calendarId, "READ")) .collect(Collectors.toList()); EventPredicateVisitor epv = new EventPredicateVisitor(true, EventPredicateVisitor.Target.NORMAL); org.jooq.Condition norCondition = null; org.jooq.Condition recCondition = null; if (conditionPredicate != null) { norCondition = BaseDAO.createCondition(conditionPredicate, epv); recCondition = BaseDAO.createCondition(conditionPredicate, new EventPredicateVisitor(true, EventPredicateVisitor.Target.RECURRING)); } DateTime from = null; DateTime to = null; DateTime instFrom = epv.hasFromRange() ? epv.getFromRange() : from; DateTime instTo = epv.hasToRange() ? epv.getToRange() : to; int noOfRecurringInst = 368; con = WT.getConnection(SERVICE_ID); if (evtDao.existByCalendarTypeCondition(con, okCalendarIds, from, to, norCondition)) { return true; } for (VVEvent vevt : evtDao.viewRecurringByCalendarRangeCondition(con, okCalendarIds, from, to, recCondition)) { List<SchedEventInstance> instances = calculateRecurringInstances(con, new SchedEventInstanceMapper(vevt), instFrom, instTo, targetTimezone, noOfRecurringInst); if (!instances.isEmpty()) return true; } return false; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public List<SchedEventInstance> listUpcomingEventInstances(Collection<Integer> calendarIds, DateTime now, DateTimeZone targetTimezone) throws WTException { return listUpcomingEventInstances(calendarIds, now, null, targetTimezone); } @Override public List<SchedEventInstance> listUpcomingEventInstances(Collection<Integer> calendarIds, DateTime now, Condition<EventQuery> conditionPredicate, DateTimeZone targetTimezone) throws WTException { return listUpcomingEventInstances(calendarIds, now, 3, conditionPredicate, targetTimezone); } @Override public List<SchedEventInstance> listUpcomingEventInstances(Collection<Integer> calendarIds, DateTime now, int days, Condition<EventQuery> conditionPredicate, DateTimeZone targetTimezone) throws WTException { if (days > 15) days = 15; DateTimeRange range = new DateTimeRange( now.withSecondOfMinute(0).withMillisOfSecond(0).withZone(targetTimezone), now.withTimeAtStartOfDay().plusDays(days)); return listEventInstances(calendarIds, range, conditionPredicate, targetTimezone, true); } @Override public List<SchedEventInstance> listEventInstances(Collection<Integer> calendarIds, DateRange range, DateTimeZone targetTimezone, boolean sort) throws WTException { return listEventInstances(calendarIds, range, null, targetTimezone, sort); } @Override public List<SchedEventInstance> listEventInstances(Collection<Integer> calendarIds, DateRange range, Condition<EventQuery> conditionPredicate, DateTimeZone targetTimezone, boolean sort) throws WTException { DateTimeRange newRange = (range == null) ? null : new DateTimeRange(range.from.toDateTimeAtStartOfDay(targetTimezone), range.to.plusDays(1).toDateTimeAtStartOfDay(targetTimezone)); return listEventInstances(calendarIds, newRange, conditionPredicate, targetTimezone, sort); } @Override public List<SchedEventInstance> listEventInstances(Collection<Integer> calendarIds, Condition<EventQuery> conditionPredicate, DateTimeZone targetTimezone) throws WTException { return listEventInstances(calendarIds, (DateTimeRange) null, conditionPredicate, targetTimezone, true); } @Override public List<SchedEventInstance> listEventInstances(Collection<Integer> calendarIds, DateTimeRange range, DateTimeZone targetTimezone, boolean sort) throws WTException { return listEventInstances(calendarIds, range, null, targetTimezone, sort); } @Override public List<SchedEventInstance> listEventInstances(Collection<Integer> calendarIds, DateTimeRange range, Condition<EventQuery> conditionPredicate, DateTimeZone targetTimezone, boolean sort) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { List<Integer> okCalendarIds = calendarIds.stream() .filter(calendarId -> quietlyCheckRightsOnCalendarFolder(calendarId, "READ")) .collect(Collectors.toList()); EventPredicateVisitor epv = new EventPredicateVisitor(true, EventPredicateVisitor.Target.NORMAL); org.jooq.Condition norCondition = null; org.jooq.Condition recCondition = null; if (conditionPredicate != null) { norCondition = BaseDAO.createCondition(conditionPredicate, epv); recCondition = BaseDAO.createCondition(conditionPredicate, new EventPredicateVisitor(true, EventPredicateVisitor.Target.RECURRING)); } boolean hasRange = (range != null); DateTime from = hasRange ? range.from : null; DateTime to = hasRange ? range.to : null; DateTime instFrom = epv.hasFromRange() ? epv.getFromRange() : from; DateTime instTo = epv.hasToRange() ? epv.getToRange() : to; int noOfRecurringInst = hasRange ? Days.daysBetween(from, to).getDays() + 2 : 368; con = WT.getConnection(SERVICE_ID); ArrayList<SchedEventInstance> instances = new ArrayList<>(); for (VVEvent vevt : evtDao.viewByCalendarRangeCondition(con, okCalendarIds, from, to, norCondition)) { SchedEventInstance item = ManagerUtils.fillSchedEvent(new SchedEventInstance(), vevt); item.setKey(EventKey.buildKey(vevt.getEventId(), vevt.getSeriesEventId())); instances.add(item); } for (VVEvent vevt : evtDao.viewRecurringByCalendarRangeCondition(con, okCalendarIds, from, to, recCondition)) { instances.addAll(calculateRecurringInstances(con, new SchedEventInstanceMapper(vevt), instFrom, instTo, targetTimezone, noOfRecurringInst)); } //TODO: transform to an ordered insert if (sort) { Collections.sort(instances, new Comparator<SchedEventInstance>() { @Override public int compare(final SchedEventInstance se1, final SchedEventInstance se2) { return se1.getStartDate().compareTo(se2.getStartDate()); } }); } return instances; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } /* public List<SchedEventInstance> toInstances(SchedEvent event, DateTime fromDate, DateTime toDate, DateTimeZone userTimezone) throws WTException { if (event.isRecurring()) { Connection con = null; try { con = WT.getConnection(SERVICE_ID); return calculateRecurringInstances(con, event, fromDate, toDate, userTimezone, 200); } catch(SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } else { return Arrays.asList(new SchedEventInstance(event)); } } */ private VVEventInstance getSchedulerEventByUid(Connection con, String eventPublicUid) throws WTException { EventDAO edao = EventDAO.getInstance(); VVEvent ve = edao.viewByPublicUid(con, eventPublicUid); if (ve == null) return null; return new VVEventInstance(ve); } /* public Integer getEventId(GetEventScope scope, boolean forceOriginal, String publicUid) throws WTException { EventDAO edao = EventDAO.getInstance(); Connection con = null; try { final String timeBasedPart = StringUtils.split(publicUid, ".", 2)[0]; final String internetName = WT.getDomainInternetName(getTargetProfileId().getDomainId()); con = WT.getConnection(SERVICE_ID); List<Integer> ids = null; if (scope.equals(GetEventScope.ALL)) { ids = edao.selectAliveIdsByPublicUid(con, publicUid); for(Integer id : ids) { if (!forceOriginal || publicUid.equals(ManagerUtils.buildEventUid(timeBasedPart, id, internetName))) { return id; } } } else { if (scope.equals(GetEventScope.PERSONAL) || scope.equals(GetEventScope.PERSONAL_AND_INCOMING)) { ids = edao.selectAliveIdsByCalendarsPublicUid(con, listCalendarIds(), publicUid); for(Integer id : ids) { if (!forceOriginal || publicUid.equals(ManagerUtils.buildEventUid(timeBasedPart, id, internetName))) { return id; } } } if (scope.equals(GetEventScope.INCOMING) || scope.equals(GetEventScope.PERSONAL_AND_INCOMING)) { ids = edao.selectAliveIdsByCalendarsPublicUid(con, listIncomingCalendarIds(), publicUid); for(Integer id : ids) { if (!forceOriginal || publicUid.equals(ManagerUtils.buildEventUid(timeBasedPart, id, internetName))) { return id; } } } } return null; } catch(SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } */ public String eventKeyByPublicUid(String eventPublicUid) throws WTException { Connection con = null; try { con = WT.getConnection(SERVICE_ID); VVEventInstance ve = getSchedulerEventByUid(con, eventPublicUid); return (ve == null) ? null : EventKey.buildKey(ve.getEventId(), ve.getSeriesEventId()); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public Event getEventFromSite(String publicUid) throws WTException { Connection con = null; // TODO: permission check try { con = WT.getConnection(SERVICE_ID); Integer eventId = doEventGetId(con, null, publicUid); if (eventId == null) throw new WTException("Event ID lookup failed [{0}]", publicUid); Event event = doEventGet(con, eventId, false, false); if (event == null) throw new WTException("Event not found [{0}]", eventId); return event; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public Event updateEventFromSite(String publicUid, String attendeeUid, EventAttendee.ResponseStatus responseStatus) throws WTException { EventAttendeeDAO evtDao = EventAttendeeDAO.getInstance(); Connection con = null; // TODO: permission check try { con = WT.getConnection(SERVICE_ID); Integer eventId = doEventGetId(con, null, publicUid); if (eventId == null) throw new WTException("Event ID lookup failed [{0}]", publicUid); int ret = evtDao.updateAttendeeResponseByIdEvent(con, EnumUtils.toSerializedName(responseStatus), attendeeUid, eventId); if (ret == 1) { Event event = doEventGet(con, eventId, false, false); if (event == null) throw new WTException("Event not found [{0}]", eventId); // Computes senderProfile: if available the calendarOnwer, // otherwise the targetProfile (in this case admin@domain) UserProfileId senderProfile = getCalendarOwner(event.getCalendarId()); if (senderProfile == null) senderProfile = getTargetProfileId(); notifyOrganizer(senderProfile, event, attendeeUid); return event; } else { return null; } } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Event getEvent(int eventId) throws WTException { return getEvent(eventId, false); } public Event getEvent(int eventId, boolean forZPushFix) throws WTException { Connection con = null; try { con = WT.getConnection(SERVICE_ID); Event event = doEventGet(con, eventId, forZPushFix ? false : true, forZPushFix); if (event == null) return null; checkRightsOnCalendarFolder(event.getCalendarId(), "READ"); return event; } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Event getEvent(GetEventScope scope, String publicUid) throws WTException { Connection con = null; try { con = WT.getConnection(SERVICE_ID); return doEventGet(con, scope, publicUid); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public EventAttachmentWithBytes getEventAttachment(int eventId, String attachmentId) throws WTException { EventDAO evtDao = EventDAO.getInstance(); EventAttachmentDAO attDao = EventAttachmentDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); Integer calId = evtDao.selectCalendarId(con, eventId); if (calId == null) return null; checkRightsOnCalendarFolder(calId, "READ"); OEventAttachment oatt = attDao.selectByIdEvent(con, attachmentId, eventId); if (oatt == null) return null; OEventAttachmentData oattData = attDao.selectBytes(con, attachmentId); return ManagerUtils.fillEventAttachment(new EventAttachmentWithBytes(oattData.getBytes()), oatt); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Event addEvent(Event event) throws WTException { return addEvent(event, true); } @Override public Event addEvent(Event event, boolean notifyAttendees) throws WTException { return addEvent(event, null, notifyAttendees); } public Event addEvent(Event event, String iCalendarRawData, boolean notifyAttendees) throws WTException { CoreManager core = WT.getCoreManager(getTargetProfileId()); CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; event.ensureCoherence(); try { checkRightsOnCalendarElements(event.getCalendarId(), "CREATE"); con = WT.getConnection(SERVICE_ID, false); String provider = calDao.selectProviderById(con, event.getCalendarId()); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", event.getCalendarId()); EventInsertResult insert = doEventInsert(con, event, iCalendarRawData, true, true, true, true); DbUtils.commitQuietly(con); writeLog("EVENT_INSERT", String.valueOf(insert.event.getEventId())); storeAsSuggestion(core, SUGGESTION_EVENT_TITLE, event.getTitle()); storeAsSuggestion(core, SUGGESTION_EVENT_LOCATION, event.getLocation()); Event eventDump = getEvent(insert.event.getEventId()); // Notify last modification List<RecipientTuple> nmRcpts = getModificationRecipients(insert.event.getCalendarId(), Crud.CREATE); if (!nmRcpts.isEmpty()) notifyForEventModification(RunContext.getRunProfileId(), nmRcpts, eventDump.getFootprint(), Crud.CREATE); // Notify attendees if (notifyAttendees) { List<RecipientTuple> attRcpts = getInvitationRecipients(getTargetProfileId(), eventDump, Crud.CREATE); if (!attRcpts.isEmpty()) notifyForInvitation(getTargetProfileId(), attRcpts, eventDump, Crud.CREATE); } //TODO: IS THIS STILL NECESSARY???????????????????????????????????????????????????????????? Event evt = ManagerUtils.createEvent(insert.event); if (insert.recurrence != null) { evt.setRecurrence(insert.recurrence.getRule(), insert.recurrence.getLocalStartDate(evt.getDateTimeZone()), null); } else { evt.setRecurrence(null, null, null); } evt.setAttendees(ManagerUtils.createEventAttendeeList(insert.attendees)); evt.setAttachments(ManagerUtils.createEventAttachmentList(insert.attachments)); return evt; } catch (SQLException | DAOException | IOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Event addEventFromICal(int calendarId, net.fortuna.ical4j.model.Calendar ical) throws WTException { final UserProfile.Data udata = WT.getUserData(getTargetProfileId()); ArrayList<EventInput> parsed = new ICalendarInput(udata.getTimeZone()).fromICalendarFile(ical, null); if (parsed.isEmpty()) throw new WTException("iCal must contain at least one event"); Event event = parsed.get(0).event; event.setCalendarId(calendarId); return addEvent(event, false); } public void updateEventOLD(Event event, boolean notifyAttendees) throws WTException { //TODO: make some tests with calDAV in order to check if can we move to updateEvent below! CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; event.ensureCoherence(); try { checkRightsOnCalendarElements(event.getCalendarId(), "UPDATE"); con = WT.getConnection(SERVICE_ID, false); String provider = calDao.selectProviderById(con, event.getCalendarId()); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", event.getCalendarId()); doEventInstanceUpdateAndCommit(con, UpdateEventTarget.ALL_SERIES, new EventKey(event.getEventId()), event, notifyAttendees); } catch (SQLException | DAOException | IOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public void updateEvent(Event event, boolean notifyAttendees) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; event.ensureCoherence(); try { checkRightsOnCalendarElements(event.getCalendarId(), "UPDATE"); con = WT.getConnection(SERVICE_ID, false); String provider = calDao.selectProviderById(con, event.getCalendarId()); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", event.getCalendarId()); doEventMasterUpdateAndCommit(con, event, notifyAttendees); } catch (SQLException | DAOException | IOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public void updateEventFromICal(net.fortuna.ical4j.model.Calendar ical) throws WTException { Connection con = null; VEvent ve = ICalendarUtils.getVEvent(ical); if (ve == null) throw new WTException("iCalendar object does not contain any events"); String uid = ICalendarUtils.getUidValue(ve); if (StringUtils.isBlank(uid)) throw new WTException("Event does not provide a valid Uid"); try { con = WT.getConnection(SERVICE_ID, false); if (ical.getMethod().equals(Method.REQUEST)) { // Organizer -> Attendee // The organizer after updating the event details send a mail message // to all attendees telling to update their saved information // Gets the event... Event evt = doEventGet(con, GetEventScope.PERSONAL_AND_INCOMING, uid); if (evt == null) throw new WTException("Event not found [{}]", uid); EventDAO edao = EventDAO.getInstance(); final UserProfile.Data udata = WT.getUserData(getTargetProfileId()); // Parse the ical using the code used in import ArrayList<EventInput> parsed = new ICalendarInput(udata.getTimeZone()).fromICalendarFile(ical, null); if (parsed.isEmpty()) throw new WTException("iCal must contain at least one event"); Event parsedEvent = parsed.get(0).event; parsedEvent.setCalendarId(evt.getCalendarId()); checkRightsOnCalendarElements(evt.getCalendarId(), "UPDATE"); OEvent original = edao.selectById(con, evt.getEventId()); // Set into parsed all fields that can't be changed by the iCal // update otherwise data can be lost inside doEventUpdate parsedEvent.setEventId(original.getEventId()); parsedEvent.setCalendarId(original.getCalendarId()); parsedEvent.setReadOnly(original.getReadOnly()); parsedEvent.setReminder(Event.Reminder.valueOf(original.getReminder())); parsedEvent.setEtag(original.getEtag()); parsedEvent.setActivityId(original.getActivityId()); parsedEvent.setMasterDataId(original.getMasterDataId()); parsedEvent.setStatMasterDataId(original.getStatMasterDataId()); parsedEvent.setCausalId(original.getCausalId()); doEventUpdate(con, original, parsedEvent, true, false); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(original.getEventId())); } else if (ical.getMethod().equals(Method.REPLY)) { // Attendee -> Organizer // The attendee replies to an event sending to the organizer a mail // message with an attached ical (the above param), properly configured. // The ical should be kept untouched in the reply except for the // attendee list: it should contains only the references of the // attendee that replies to the invitation. Attendee att = ICalendarUtils.getAttendee(ve); if (att == null) throw new WTException("Event does not provide any attendees"); // Gets the event looking also into incoming calendars... // (i can be the organizer of a meeting created for my boss that // share his calendar with me; all received replies must be bringed // back to the event in the shared calendar) //Event evt = getEvent(uid); // Previous impl. forced (forceOriginal == true) Event evt = doEventGet(con, GetEventScope.PERSONAL_AND_INCOMING, uid); if (evt == null) throw new WTException("Event not found [{0}]", uid); // Extract response info... PartStat partStat = (PartStat) att.getParameter(Parameter.PARTSTAT); ICalendarInput inWithDummyTz = new ICalendarInput(DateTimeZone.UTC); List<String> updatedAttIds = doEventAttendeeUpdateResponseByRecipient(con, evt, att.getCalAddress().getSchemeSpecificPart(), inWithDummyTz.partStatToResponseStatus(partStat)); //List<String> updatedAttIds = updateEventAttendeeResponseByRecipient(evt, att.getCalAddress().getSchemeSpecificPart(), responseStatus); DbUtils.commitQuietly(con); // Commented to not send notification email in this case: // the organizer already knows this info, he updated 'manually' the // event by clicking the "Update event" button on the preview! /* if (!updatedAttIds.isEmpty()) { evt = getEvent(evt.getEventId()); for(String attId : updatedAttIds) notifyOrganizer(getLocale(), evt, attId); } */ } else if (ical.getMethod().equals(Method.CANCEL)) { // Organizer -> Attendee // The organizer after cancelling the event send a mail message // to all attendees telling to update their saved information // Gets the event... //Event evt = getEventForICalUpdate(uid); Event evt = doEventGet(con, GetEventScope.PERSONAL_AND_INCOMING, uid); if (evt == null) throw new WTException("Event not found [{0}]", uid); doEventDelete(con, evt.getEventId(), true); DbUtils.commitQuietly(con); writeLog("EVENT_DELETE", String.valueOf(evt.getEventId())); } else { throw new WTException("Unsupported Calendar's method [{0}]", ical.getMethod().toString()); } } catch (SQLException | DAOException | IOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public void deleteEvent(int eventId, boolean notifyAttendees) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID, false); Integer calendarId = evtDao.selectCalendarId(con, eventId); if (calendarId == null) throw new WTException("Unable to retrieve event [{}]", eventId); checkRightsOnCalendarElements(calendarId, "DELETE"); String provider = calDao.selectProviderById(con, calendarId); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", calendarId); doEventInstanceDeleteAndCommit(con, UpdateEventTarget.ALL_SERIES, new EventKey(eventId), notifyAttendees); } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } private List<String> doEventAttendeeUpdateResponseByRecipient(Connection con, Event event, String recipient, EventAttendee.ResponseStatus responseStatus) throws WTException { EventDAO evtDao = EventDAO.getInstance(); EventAttendeeDAO attDao = EventAttendeeDAO.getInstance(); // Find matching attendees ArrayList<String> matchingIds = new ArrayList<>(); List<OEventAttendee> atts = attDao.selectByEvent(con, event.getEventId()); for (OEventAttendee att : atts) { final InternetAddress ia = InternetAddressUtils.toInternetAddress(att.getRecipient()); if (ia == null) continue; if (StringUtils.equalsIgnoreCase(ia.getAddress(), recipient)) matchingIds.add(att.getAttendeeId()); } // Update responses int ret = attDao.updateAttendeeResponseByIds(con, EnumUtils.toSerializedName(responseStatus), matchingIds); evtDao.updateRevision(con, event.getEventId(), BaseDAO.createRevisionTimestamp()); if (matchingIds.size() == ret) { return matchingIds; } else { throw new WTException("# of attendees to update don't match the uptated ones"); } } @Override public String getEventInstanceKey(int eventId) throws WTException { EventDAO edao = EventDAO.getInstance(); RecurrenceDAO rdao = RecurrenceDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID); OEvent oevt = edao.selectById(con, eventId); if (oevt == null) return null; //TODO: completare implementazione differenziando eventi singoli, broken ed istanze ricorrenti... return EventKey.buildKey(oevt.getEventId(), oevt.getEventId()); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public EventInstance getEventInstance(String eventKey) throws WTException { return getEventInstance(new EventKey(eventKey)); } public EventInstance getEventInstance(EventKey key) throws WTException { Connection con = null; try { con = WT.getConnection(SERVICE_ID); return doEventInstanceGet(con, key.eventId, key.instanceDate, true); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public void updateEventInstance(UpdateEventTarget target, EventInstance event, boolean notifyAttendees) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); EventDAO evtDao = EventDAO.getInstance(); Connection con = null; event.ensureCoherence(); try { con = WT.getConnection(SERVICE_ID, false); Integer calendarId = evtDao.selectCalendarId(con, event.getEventId()); if (calendarId == null) throw new WTException("Unable to retrieve event [{}]", event.getEventId()); checkRightsOnCalendarElements(calendarId, "UPDATE"); String provider = calDao.selectProviderById(con, calendarId); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", calendarId); // TODO: avoid this!!! EventKey eventKey = new EventKey(event.getKey()); doEventInstanceUpdateAndCommit(con, target, eventKey, event, notifyAttendees); } catch (SQLException | DAOException | IOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public void updateEventInstance(UpdateEventTarget target, EventKey key, DateTime newStart, DateTime newEnd, String newTitle, boolean notifyAttendees) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID, false); EventInstance ei = doEventInstanceGet(con, key.eventId, key.instanceDate, true); int calendarId = ei.getCalendarId(); checkRightsOnCalendarElements(calendarId, "UPDATE"); String provider = calDao.selectProviderById(con, calendarId); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", calendarId); if ((newStart != null) && (newEnd != null)) { ei.setStartDate(newStart); ei.setEndDate(newEnd); } else if (newStart != null) { Duration length = new Duration(ei.getStartDate(), ei.getEndDate()); ei.setStartDate(newStart); ei.setEndDate(newStart.plus(length)); } else if (newEnd != null) { Duration length = new Duration(ei.getStartDate(), ei.getEndDate()); ei.setStartDate(newEnd.minus(length)); ei.setEndDate(newEnd); } if (newTitle != null) { ei.setTitle(newTitle); } ei.ensureCoherence(); doEventInstanceUpdateAndCommit(con, target, key, ei, notifyAttendees); } catch (SQLException | DAOException | IOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } /* public void updateEventInstance(String eventKey, DateTime startDate, DateTime endDate, String title, boolean notifyAttendees) throws WTException { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { EventKey ekey = new EventKey(eventKey); con = WT.getConnection(SERVICE_ID, false); VVEvent vevt = evtDao.viewById(con, ekey.eventId); if (vevt == null) throw new WTException("Unable to retrieve event [{}]", ekey.eventId); checkRightsOnCalendarElements(vevt.getCalendarId(), "UPDATE"); if (vevt.isEventRecurring()) { //TODO: add support to recurring events throw new WTException("Not supported on recurring events [{}]", ekey.eventId); } else { // 1 - Updates event's dates/times (+revision) OEvent oevt = evtDao.selectById(con, ekey.eventId); if (oevt == null) throw new WTException("Unable to retrieve event [{}]", ekey.eventId); oevt.setStartDate(startDate); oevt.setEndDate(endDate); oevt.setTitle(title); oevt.ensureCoherence(); evtDao.update(con, oevt, BaseDAO.createRevisionTimestamp(), oevt.getStartDate().isAfterNow()); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(oevt.getEventId())); EventInstance eventDump = getEventInstance(eventKey); // Notify last modification List<RecipientTuple> nmRcpts = getModificationRecipients(oevt.getCalendarId(), Crud.UPDATE); if (!nmRcpts.isEmpty()) notifyForEventModification(RunContext.getRunProfileId(), nmRcpts, eventDump.getFootprint(), Crud.UPDATE); // Notify attendees if (notifyAttendees) { List<RecipientTuple> attRcpts = getInvitationRecipients(getTargetProfileId(), eventDump, Crud.UPDATE); if (!attRcpts.isEmpty()) notifyForInvitation(getTargetProfileId(), attRcpts, eventDump, Crud.UPDATE); } } } catch(SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } */ @Override public void deleteEventInstance(UpdateEventTarget target, String eventKey, boolean notifyAttendees) throws WTException { deleteEventInstance(target, new EventKey(eventKey), notifyAttendees); } @Override public void deleteEventInstance(UpdateEventTarget target, EventKey key, boolean notifyAttendees) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID, false); Integer calendarId = evtDao.selectCalendarId(con, key.eventId); if (calendarId == null) throw new WTException("Unable to retrieve event [{}]", key.eventId); checkRightsOnCalendarElements(calendarId, "DELETE"); String provider = calDao.selectProviderById(con, calendarId); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", calendarId); doEventInstanceDeleteAndCommit(con, target, key, notifyAttendees); } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public void restoreEventInstance(EventKey key) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID, false); Integer calendarId = evtDao.selectCalendarId(con, key.eventId); if (calendarId == null) throw new WTException("Unable to retrieve event [{}]", key.eventId); checkRightsOnCalendarElements(calendarId, "UPDATE"); String provider = calDao.selectProviderById(con, calendarId); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", calendarId); doEventInstanceRestoreAndCommit(con, key, true); } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } @Override public Event cloneEventInstance(EventKey key, Integer newCalendarId, DateTime newStart, DateTime newEnd, boolean notifyAttendees) throws WTException { Connection con = null; EventInstance ei = null; try { con = WT.getConnection(SERVICE_ID); ei = doEventInstanceGet(con, key.eventId, key.instanceDate, true); checkRightsOnCalendarFolder(ei.getCalendarId(), "READ"); int calendarId = (newCalendarId != null) ? newCalendarId : ei.getCalendarId(); ei.setCalendarId(calendarId); ei.setPublicUid(null); // Reset value in order to make inner function generate new one! ei.setHref(null); // Reset value in order to make inner function generate new one! if ((newStart != null) && (newEnd != null)) { ei.setStartDate(newStart); ei.setEndDate(newEnd); } else if (newStart != null) { Duration length = new Duration(ei.getStartDate(), ei.getEndDate()); ei.setStartDate(newStart); ei.setEndDate(newStart.plus(length)); } else if (newEnd != null) { Duration length = new Duration(ei.getStartDate(), ei.getEndDate()); ei.setStartDate(newEnd.minus(length)); ei.setEndDate(newEnd); } } catch (SQLException | DAOException | WTException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } return addEvent(ei, notifyAttendees); } @Override public void moveEventInstance(EventKey key, int targetCalendarId) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; EventInstance ei = null; try { con = WT.getConnection(SERVICE_ID, false); ei = doEventInstanceGet(con, key.eventId, key.instanceDate, true); checkRightsOnCalendarFolder(ei.getCalendarId(), "READ"); if (targetCalendarId != ei.getCalendarId()) { checkRightsOnCalendarElements(ei.getCalendarId(), "DELETE"); String provider = calDao.selectProviderById(con, targetCalendarId); if (Calendar.isProviderRemote(provider)) throw new WTException("Calendar is remote and therefore read-only [{}]", targetCalendarId); doEventMove(con, false, ei, targetCalendarId); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(ei.getEventId())); // Notify last modification List<RecipientTuple> nmRcpts = getModificationRecipients(ei.getCalendarId(), Crud.DELETE); if (!nmRcpts.isEmpty()) notifyForEventModification(RunContext.getRunProfileId(), nmRcpts, ei.getFootprint(), Crud.DELETE); // There is no need to notify attendees, the event is simply moved from a calendar to another! } } catch (SQLException | DAOException | IOException | WTException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public LinkedHashSet<String> calculateAvailabilitySpans(int minRange, UserProfileId pid, DateTime fromDate, DateTime toDate, DateTimeZone userTz, boolean busy) throws WTException { CalendarDAO calDao = CalendarDAO.getInstance(); EventDAO evtDao = EventDAO.getInstance(); LinkedHashSet<String> hours = new LinkedHashSet<>(); Connection con = null; //TODO: review this method try { con = WT.getConnection(SERVICE_ID); // Lists desired calendars by profile final List<VVEventInstance> veis = new ArrayList<>(); for (OCalendar ocal : calDao.selectByProfile(con, pid.getDomainId(), pid.getUserId())) { for (VVEvent ve : evtDao.viewByCalendarRangeCondition(con, ocal.getCalendarId(), fromDate, toDate, null)) { veis.add(new VVEventInstance(ve)); } for (VVEvent ve : evtDao.viewRecurringByCalendarRangeCondition(con, ocal.getCalendarId(), fromDate, toDate, null)) { veis.add(new VVEventInstance(ve)); } } DateTime startDt, endDt; for (VVEventInstance vei : veis) { if (vei.getBusy() != busy) continue; // Ignore events that are not marked as busy! if (vei.getRecurrenceId() == null) { startDt = vei.getStartDate().withZone(userTz); endDt = vei.getEndDate().withZone(userTz); hours.addAll(generateTimeSpans(minRange, startDt.toLocalDate(), endDt.toLocalDate(), startDt.toLocalTime(), endDt.toLocalTime(), userTz)); } else { final List<VVEventInstance> instances = calculateRecurringInstances(con, new VVEventInstanceMapper(vei), fromDate, toDate, userTz); for (VVEventInstance instance : instances) { startDt = instance.getStartDate().withZone(userTz); endDt = instance.getEndDate().withZone(userTz); hours.addAll(generateTimeSpans(minRange, startDt.toLocalDate(), endDt.toLocalDate(), startDt.toLocalTime(), endDt.toLocalTime(), userTz)); } } } } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } return hours; } public ArrayList<String> generateTimeSpans(int minRange, LocalDate fromDate, LocalDate toDate, LocalTime fromTime, LocalTime toTime, DateTimeZone tz) { ArrayList<String> hours = new ArrayList<>(); DateTimeFormatter ymdhmZoneFmt = DateTimeUtils.createYmdHmFormatter(tz); DateTime instant = new DateTime(tz).withDate(fromDate).withTime(fromTime).withSecondOfMinute(0) .withMillisOfSecond(0); DateTime boundaryInstant = new DateTime(tz).withDate(toDate).withTime(toTime).withSecondOfMinute(0) .withMillisOfSecond(0); while (instant.compareTo(boundaryInstant) < 0) { hours.add(ymdhmZoneFmt.print(instant)); instant = instant.plusMinutes(minRange); } return hours; } public List<EventAttendee> listEventAttendees(int eventId, boolean notifiedOnly) throws WTException { Connection con = null; try { con = WT.getConnection(SERVICE_ID); return getEventAttendees(con, eventId, notifiedOnly); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } private List<EventAttendee> getEventAttendees(Connection con, int eventId, boolean notifiedOnly) throws WTException { List<OEventAttendee> attendees = null; EventAttendeeDAO eadao = EventAttendeeDAO.getInstance(); if (notifiedOnly) { attendees = eadao.selectByEventNotify(con, eventId, true); } else { attendees = eadao.selectByEvent(con, eventId); } return ManagerUtils.createEventAttendeeList(attendees); } public LogEntries importEvents(int calendarId, EventFileReader rea, File file, String mode) throws WTException { LogEntries log = new LogEntries(); HashMap<String, OEvent> uidMap = new HashMap<>(); Connection con = null; try { checkRightsOnCalendarElements(calendarId, "CREATE"); if (mode.equals("copy")) checkRightsOnCalendarElements(calendarId, "DELETE"); log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "Started at {0}", new DateTime())); log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "Reading source file...")); ArrayList<EventInput> input = null; try { input = rea.listEvents(log, file); } catch (IOException | UnsupportedOperationException ex) { log.addMaster(new MessageLogEntry(LogEntry.Level.ERROR, "Unable to complete reading. Reason: {0}", ex.getMessage())); throw new WTException(ex); } log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "{0} event/s found!", input.size())); con = WT.getConnection(SERVICE_ID, false); if (mode.equals("copy")) { log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "Cleaning previous events...")); int del = doEventsDeleteByCalendar(con, calendarId, false); log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "{0} event/s deleted!", del)); DbUtils.commitQuietly(con); } log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "Importing...")); int count = 0; for (EventInput ei : input) { ei.event.setCalendarId(calendarId); try { doEventInputInsert(con, uidMap, ei); DbUtils.commitQuietly(con); count++; } catch (Exception ex) { logger.trace("Error inserting event", ex); DbUtils.rollbackQuietly(con); log.addMaster(new MessageLogEntry(LogEntry.Level.ERROR, "Unable to import event [{0}, {1}]. Reason: {2}", ei.event.getTitle(), ei.event.getPublicUid(), ex.getMessage())); } } log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "{0} event/s imported!", count)); } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); log.addMaster(new MessageLogEntry(LogEntry.Level.INFO, "Ended at {0}", new DateTime())); } return log; } public void exportEvents(LogEntries log, DateTime fromDate, DateTime toDate, OutputStream os) throws Exception { Connection con = null, ccon = null; ICsvMapWriter mapw = null; UserDAO userDao = UserDAO.getInstance(); CalendarDAO calDao = CalendarDAO.getInstance(); EventDAO edao = EventDAO.getInstance(); try { //TODO: Gestire campi visit_id e action_id provenienti dal servizio DRM final String dateFmt = "yyyy-MM-dd"; final String timeFmt = "HH:mm:ss"; final String[] headers = new String[] { "eventId", "userId", "userDescription", "startDate", "startTime", "endDate", "endTime", "timezone", "duration", "title", "description", "activityId", "activityDescription", "activityExternalId", "causalId", "causalDescription", "causalExternalId", "masterDataId", "masterDataDescription" }; final CellProcessor[] processors = new CellProcessor[] { new NotNull(), new NotNull(), null, new FmtDateTime(dateFmt), new FmtDateTime(timeFmt), new FmtDateTime(dateFmt), new FmtDateTime(timeFmt), new NotNull(), new NotNull(), new NotNull(), null, null, null, null, null, null, null, null, null }; CsvPreference pref = new CsvPreference.Builder('"', ';', "\n").build(); mapw = new CsvMapWriter(new OutputStreamWriter(os), pref); mapw.writeHeader(headers); CoreManager coreMgr = WT.getCoreManager(getTargetProfileId()); con = WT.getConnection(SERVICE_ID); ccon = WT.getCoreConnection(); HashMap<String, Object> map = null; for (OCalendar ocal : calDao.selectByDomain(con, getTargetProfileId().getDomainId())) { final UserProfileId pid = new UserProfileId(ocal.getDomainId(), ocal.getUserId()); final OUser user = userDao.selectByDomainUser(ccon, ocal.getDomainId(), ocal.getUserId()); if (user == null) throw new WTException("User [{0}] not found", pid.toString()); final UserProfile.Data udata = WT.getUserData(pid); for (VVEvent ve : edao.viewByCalendarRangeCondition(con, ocal.getCalendarId(), fromDate, toDate, null)) { final VVEventInstance vei = new VVEventInstance(ve); try { map = new HashMap<>(); map.put("userId", user.getUserId()); map.put("descriptionId", user.getDisplayName()); fillExportMapBasic(map, coreMgr, con, vei); fillExportMapDates(map, udata.getTimeZone(), vei); mapw.write(map, headers, processors); } catch (Exception ex) { log.addMaster(new MessageLogEntry(LogEntry.Level.ERROR, "Event skipped [{0}]. Reason: {1}", ve.getEventId(), ex.getMessage())); } } for (VVEvent ve : edao.viewRecurringByCalendarRangeCondition(con, ocal.getCalendarId(), fromDate, toDate, null)) { final List<VVEventInstance> instances = calculateRecurringInstances(con, new VVEventInstanceMapper(ve), fromDate, toDate, udata.getTimeZone()); try { map = new HashMap<>(); map.put("userId", user.getUserId()); map.put("descriptionId", user.getDisplayName()); fillExportMapBasic(map, coreMgr, con, ve); for (VVEventInstance vei : instances) { fillExportMapDates(map, udata.getTimeZone(), vei); mapw.write(map, headers, processors); } } catch (Exception ex) { log.addMaster(new MessageLogEntry(LogEntry.Level.ERROR, "Event skipped [{0}]. Reason: {1}", ve.getEventId(), ex.getMessage())); } } } mapw.flush(); } catch (Exception ex) { throw ex; } finally { DbUtils.closeQuietly(con); DbUtils.closeQuietly(ccon); try { if (mapw != null) mapw.close(); } catch (Exception ex) { /* Do nothing... */ } } } public void eraseData(boolean deep) throws WTException { CalendarDAO caldao = CalendarDAO.getInstance(); CalendarPropsDAO psetDao = CalendarPropsDAO.getInstance(); EventDAO evtdao = EventDAO.getInstance(); EventAttendeeDAO attdao = EventAttendeeDAO.getInstance(); RecurrenceDAO recdao = RecurrenceDAO.getInstance(); RecurrenceBrokenDAO recbrkdao = RecurrenceBrokenDAO.getInstance(); Connection con = null; //TODO: controllo permessi try { con = WT.getConnection(SERVICE_ID, false); UserProfileId pid = getTargetProfileId(); // Erase events and related tables if (deep) { for (OCalendar ocal : caldao.selectByProfile(con, pid.getDomainId(), pid.getUserId())) { attdao.deleteByCalendar(con, ocal.getCalendarId()); recdao.deleteByCalendar(con, ocal.getCalendarId()); recbrkdao.deleteByCalendar(con, ocal.getCalendarId()); evtdao.deleteByCalendar(con, ocal.getCalendarId()); } } else { DateTime revTs = BaseDAO.createRevisionTimestamp(); for (OCalendar ocal : caldao.selectByProfile(con, pid.getDomainId(), pid.getUserId())) { evtdao.logicDeleteByCalendar(con, ocal.getCalendarId(), revTs); } } // Erase calendars psetDao.deleteByProfile(con, pid.getDomainId(), pid.getUserId()); caldao.deleteByProfile(con, pid.getDomainId(), pid.getUserId()); DbUtils.commitQuietly(con); } catch (SQLException | DAOException ex) { DbUtils.rollbackQuietly(con); throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } public List<BaseReminder> getRemindersToBeNotified(DateTime now) { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; ensureSysAdmin(); ArrayList<VExpEventInstance> evtInstCandidates = new ArrayList<>(); logger.trace("Analyzing event instances..."); try { final DateTime from = now.withTimeAtStartOfDay(); con = WT.getConnection(SERVICE_ID, false); for (VExpEventInstance evtInst : doEventGetExpiredForUpdate(con, from, from.plusDays(7 * 2 + 1))) { final DateTime remindOn = evtInst.getStartDate().withZone(DateTimeZone.UTC) .minusMinutes(evtInst.getReminder()); if (now.compareTo(remindOn) < 0) continue; // If instance should have been reminded in past... if (evtInst.getRemindedOn() != null) { // Only recurring event instances should pass here, classic events are already excluded by the db query if (evtInst.getRecurrenceId() == null) throw new WTException("This should never happen (famous last words)"); final DateTime lastRemindedOn = evtInst.getRemindedOn().withZone(DateTimeZone.UTC); if (remindOn.compareTo(lastRemindedOn) <= 0) continue; // If instance should have been reminded after last remind... } int ret = evtDao.updateRemindedOn(con, evtInst.getEventId(), now); evtInstCandidates.add(evtInst); } DbUtils.commitQuietly(con); } catch (SQLException | DAOException | WTException ex) { DbUtils.rollbackQuietly(con); logger.error("Error collecting instances", ex); } finally { DbUtils.closeQuietly(con); } logger.debug("Found {} instances to be reminded", evtInstCandidates.size()); ArrayList<BaseReminder> alerts = new ArrayList<>(); HashMap<UserProfileId, Boolean> byEmailCache = new HashMap<>(); logger.trace("Preparing alerts..."); for (VExpEventInstance evtInst : evtInstCandidates) { logger.debug("Working on instance [{}, {}]", evtInst.getEventId(), evtInst.getStartDate()); if (!byEmailCache.containsKey(evtInst.getCalendarProfileId())) { CalendarUserSettings cus = new CalendarUserSettings(SERVICE_ID, evtInst.getCalendarProfileId()); boolean bool = cus.getEventReminderDelivery() .equals(CalendarSettings.EVENT_REMINDER_DELIVERY_EMAIL); byEmailCache.put(evtInst.getCalendarProfileId(), bool); } if (byEmailCache.get(evtInst.getCalendarProfileId())) { UserProfile.Data ud = WT.getUserData(evtInst.getCalendarProfileId()); CoreUserSettings cus = new CoreUserSettings(evtInst.getCalendarProfileId()); try { EventInstance eventInstance = getEventInstance(evtInst.getKey()); alerts.add(createEventReminderAlertEmail(ud.getLocale(), cus.getShortDateFormat(), cus.getShortTimeFormat(), ud.getPersonalEmailAddress(), evtInst.getCalendarProfileId(), eventInstance)); } catch (WTException ex) { logger.error("Error preparing email", ex); } } else { alerts.add(createEventReminderAlertWeb(evtInst)); } } //FIXME: remove this when zpush is using manager methods sendInvitationForZPushEvents(); // ---------------------------- return alerts; } private void sendInvitationForZPushEvents() { EventDAO evtDao = EventDAO.getInstance(); Connection con = null; try { con = WT.getConnection(SERVICE_ID, false); ArrayList<Integer> processed = new ArrayList<>(); for (OEvent oevt : evtDao.selectHandleInvitationByRevision(con)) { Event.RevisionStatus revStatus = EnumUtils.forSerializedName(oevt.getRevisionStatus(), Event.RevisionStatus.class); String crud = null; if (Event.RevisionStatus.NEW.equals(revStatus)) { crud = Crud.CREATE; } else if (Event.RevisionStatus.MODIFIED.equals(revStatus)) { crud = Crud.UPDATE; } else if (Event.RevisionStatus.DELETED.equals(revStatus)) { crud = Crud.DELETE; } if (crud == null) { logger.warn("Invalid revision status [{}]", oevt.getEventId()); continue; } Event event = getEvent(oevt.getEventId(), true); if (event == null) { logger.warn("Event not found [{}]", oevt.getEventId()); continue; } Calendar calendar = getCalendar(oevt.getCalendarId()); if (calendar == null) { logger.warn("Calendar not found [{}]", oevt.getCalendarId()); } else { //notifyAttendees(calendar.getProfileId(), crud, event); List<RecipientTuple> attRcpts = getInvitationRecipients(getTargetProfileId(), event, crud); if (!attRcpts.isEmpty()) notifyForInvitation(getTargetProfileId(), attRcpts, event, crud); } processed.add(oevt.getEventId()); } evtDao.updateHandleInvitationIn(con, processed, false); DbUtils.commitQuietly(con); } catch (Exception ex) { DbUtils.rollbackQuietly(con); logger.error("Error collecting reminder alerts", ex); } finally { DbUtils.closeQuietly(con); } } public ProbeCalendarRemoteUrlResult probeCalendarRemoteUrl(Calendar.Provider provider, URI url, String username, String password) throws WTException { if (!Calendar.isProviderRemote(provider)) { throw new WTException("Provider is not remote (webcal or CalDAV) [{}]", EnumUtils.toSerializedName(provider)); } try { if (Calendar.Provider.WEBCAL.equals(provider)) { URIBuilder builder = new URIBuilder(url); if (StringUtils.equalsIgnoreCase(builder.getScheme(), "webcal")) { builder.setScheme("http"); // Force http scheme } if (!StringUtils.isBlank(username) && !StringUtils.isBlank(username)) { builder.setUserInfo(username, password); } URI newUrl = URIUtils.buildQuietly(builder); HttpClient httpCli = null; try { logger.debug("Checking remote calendar URL [{}]", newUrl.toString()); httpCli = HttpClientUtils.createBasicHttpClient(HttpClientUtils.configureSSLAcceptAll(), newUrl); return HttpClientUtils.exists(httpCli, newUrl) ? new ProbeCalendarRemoteUrlResult(FilenameUtils.getBaseName(newUrl.getPath())) : null; } finally { HttpClientUtils.closeQuietly(httpCli); } } else if (Calendar.Provider.CALDAV.equals(provider)) { CalDav dav = getCalDav(username, password); try { DavCalendar dcal = dav.getCalendar(url.toString()); return (dcal != null) ? new ProbeCalendarRemoteUrlResult(dcal.getDisplayName()) : null; } catch (DavException ex) { logger.error("DAV error", ex); return null; } } else { throw new WTException("Unsupported provider"); } } catch (IOException ex) { throw new WTException(ex, "Unable to check URL [{0}]", url.toString()); } } public void syncRemoteCalendar(int calendarId, boolean full) throws WTException { final UserProfile.Data udata = WT.getUserData(getTargetProfileId()); final ICalendarInput icalInput = new ICalendarInput(udata.getTimeZone()); final String PENDING_KEY = String.valueOf(calendarId); CalendarDAO calDao = CalendarDAO.getInstance(); Connection con = null; if (pendingRemoteCalendarSyncs.putIfAbsent(PENDING_KEY, RunContext.getRunProfileId()) != null) { throw new ConcurrentSyncException("Sync activity is already running [{}, {}]", calendarId, RunContext.getRunProfileId()); } try { //checkRightsOnCalendarFolder(calendarId, "READ"); con = WT.getConnection(SERVICE_ID, false); Calendar cal = ManagerUtils.createCalendar(calDao.selectById(con, calendarId)); if (cal == null) throw new WTException("Calendar not found [{0}]", calendarId); if (!Calendar.Provider.WEBCAL.equals(cal.getProvider()) && !Calendar.Provider.CALDAV.equals(cal.getProvider())) { throw new WTException("Specified calendar is not remote (webcal or CalDAV) [{0}]", calendarId); } // Force a full update if last-sync date is null if (cal.getRemoteSyncTimestamp() == null) full = true; CalendarRemoteParameters params = LangUtils.deserialize(cal.getParameters(), CalendarRemoteParameters.class); if (params == null) throw new WTException("Unable to deserialize remote parameters"); if (params.url == null) throw new WTException("Remote URL is undefined"); if (Calendar.Provider.WEBCAL.equals(cal.getProvider())) { final String PREFIX = "webcal-"; File tempFile = null; URIBuilder builder = new URIBuilder(params.url); if (StringUtils.equalsIgnoreCase(builder.getScheme(), "webcal")) { builder.setScheme("http"); // Force http scheme } if (!StringUtils.isBlank(params.username) && !StringUtils.isBlank(params.username)) { builder.setUserInfo(params.username, params.password); } URI newUrl = URIUtils.buildQuietly(builder); try { final DateTime newLastSync = DateTimeUtils.now(); tempFile = WT.createTempFile(PREFIX, null); // Retrieve webcal content (iCalendar) from the specified URL // and save it locally logger.debug("Downloading iCalendar file from URL [{}]", newUrl); HttpClient httpCli = null; FileOutputStream os = null; try { httpCli = HttpClientUtils.createBasicHttpClient(HttpClientUtils.configureSSLAcceptAll(), newUrl); os = new FileOutputStream(tempFile); HttpClientUtils.writeContent(httpCli, newUrl, os); } catch (IOException ex) { throw new WTException(ex, "Unable to retrieve webcal [{0}]", newUrl); } finally { IOUtils.closeQuietly(os); HttpClientUtils.closeQuietly(httpCli); } logger.debug("Saved to temp file [{}]", tempFile.getName()); // Parse downloaded iCalendar logger.debug("Parsing downloaded iCalendar file"); net.fortuna.ical4j.model.Calendar ical = null; FileInputStream is = null; try { is = new FileInputStream(tempFile); ICalendarUtils.relaxParsingAndCompatibility(); ical = ICalendarUtils.parse(is); //TODO: add support to FILENAME property (Google https://github.com/ical4j/ical4j/issues/69) } catch (IOException | ParserException ex) { throw new WTException(ex, "Unable to read webcal"); } finally { IOUtils.closeQuietly(os); } icalInput.withIncludeVEventSourceInOutput(true); ArrayList<EventInput> input = icalInput.fromICalendarFile(ical, null); logger.debug("Found {} events", input.size()); Map<String, VEventHrefSync> syncByHref = null; if (full) { logger.debug("Cleaning up calendar [{}]", calendarId); doEventsDeleteByCalendar(con, calendarId, false); } else { EventDAO evtDao = EventDAO.getInstance(); syncByHref = evtDao.viewHrefSyncDataByCalendar(con, calendarId); } // Inserts/Updates data... logger.debug("Inserting/Updating events..."); try { String autoUidPrefix = DigestUtils.md5Hex(newUrl.toString()); // auto-gen base prefix in case of missing UID HashSet<String> hrefs = new HashSet<>(); HashMap<String, OEvent> cache = new HashMap<>(); int i = 0; for (EventInput ei : input) { if (StringUtils.isBlank(ei.event.getPublicUid())) { String autoUid = autoUidPrefix + "-" + i; ei.event.setPublicUid(autoUid); logger.trace("Missing UID: using auto-gen value. [{}]", autoUid); } String href = ManagerUtils.buildHref(ei.event.getPublicUid()); //if (logger.isTraceEnabled()) logger.trace("{}", ICalendarUtils.print(ICalendarUtils.getVEvent(devt.getCalendar()))); if (hrefs.contains(href)) { logger.trace("Event duplicated. Skipped! [{}]", href); continue; } boolean skip = false; Integer matchingEventId = null; String eiHash = DigestUtils.md5Hex(ei.sourceEvent.toString()); if (syncByHref != null) { // Only if... (!full) see above! VEventHrefSync hrefSync = syncByHref.remove(href); if (hrefSync != null) { // Href found -> maybe updated item if (!StringUtils.equals(hrefSync.getEtag(), eiHash)) { matchingEventId = hrefSync.getEventId(); logger.trace("Event updated [{}, {}]", href, eiHash); } else { skip = true; logger.trace("Event not modified [{}, {}]", href, eiHash); } } else { // Href not found -> added item logger.trace("Event newly added [{}, {}]", href, eiHash); } } if (!skip) { ei.event.setCalendarId(calendarId); ei.event.setHref(href); ei.event.setEtag(eiHash); if (matchingEventId != null) { ei.event.setEventId(matchingEventId); boolean updated = doEventInputUpdate(con, cache, ei); if (!updated) throw new WTException("Event not found [{}]", ei.event.getEventId()); } else { doEventInputInsert(con, cache, ei); } } hrefs.add(href); // Marks as processed! } if (syncByHref != null) { // Only if... (!full) see above! // Remaining hrefs -> deleted items for (VEventHrefSync hrefSync : syncByHref.values()) { logger.trace("Event deleted [{}]", hrefSync.getHref()); doEventDelete(con, hrefSync.getEventId(), false); } } cache.clear(); calDao.updateRemoteSyncById(con, calendarId, newLastSync, null); DbUtils.commitQuietly(con); } catch (Exception ex) { DbUtils.rollbackQuietly(con); throw new WTException(ex, "Error importing iCalendar"); } } finally { if (tempFile != null) { logger.debug("Removing temp file [{}]", tempFile.getName()); WT.deleteTempFile(tempFile); } } } else if (Calendar.Provider.CALDAV.equals(cal.getProvider())) { CalDav dav = getCalDav(params.username, params.password); try { DavCalendar dcal = dav.getCalendarSyncToken(params.url.toString()); if (dcal == null) throw new WTException("DAV calendar not found"); final boolean syncIsSupported = !StringUtils.isBlank(dcal.getSyncToken()); final DateTime newLastSync = DateTimeUtils.now(); if (!full && (syncIsSupported && !StringUtils.isBlank(cal.getRemoteSyncTag()))) { // Partial update using SYNC mode String newSyncToken = dcal.getSyncToken(); logger.debug("Querying CalDAV endpoint for changes [{}, {}]", params.url.toString(), cal.getRemoteSyncTag()); List<DavSyncStatus> changes = dav.getCalendarChanges(params.url.toString(), cal.getRemoteSyncTag()); logger.debug("Returned {} items", changes.size()); try { if (!changes.isEmpty()) { EventDAO evtDao = EventDAO.getInstance(); Map<String, List<Integer>> eventIdsByHref = evtDao.selectHrefsByByCalendar(con, calendarId); // Process changes... logger.debug("Processing changes..."); HashSet<String> hrefs = new HashSet<>(); for (DavSyncStatus change : changes) { String href = FilenameUtils.getName(change.getPath()); //String href = change.getPath(); if (DavUtil.HTTP_SC_TEXT_OK.equals(change.getResponseStatus())) { hrefs.add(href); } else { // Event deleted List<Integer> eventIds = eventIdsByHref.get(href); Integer eventId = (eventIds != null) ? eventIds.get(eventIds.size() - 1) : null; if (eventId == null) { logger.warn("Deletion not possible. Event path not found [{}]", PathUtils.concatPaths(dcal.getPath(), FilenameUtils.getName(href))); continue; } doEventDelete(con, eventId, false); } } // Retrieves events list from DAV endpoint (using multiget) logger.debug("Retrieving inserted/updated events [{}]", hrefs.size()); Collection<String> paths = hrefs.stream().map( href -> PathUtils.concatPaths(dcal.getPath(), FilenameUtils.getName(href))) .collect(Collectors.toList()); List<DavCalendarEvent> devts = dav.listCalendarEvents(params.url.toString(), paths); //List<DavCalendarEvent> devts = dav.listCalendarEvents(params.url.toString(), hrefs); // Inserts/Updates data... logger.debug("Inserting/Updating events..."); HashMap<String, OEvent> cache = new HashMap<>(); for (DavCalendarEvent devt : devts) { String href = FilenameUtils.getName(devt.getPath()); //String href = devt.getPath(); if (logger.isTraceEnabled()) logger.trace("{}", ICalendarUtils.print(ICalendarUtils.getVEvent(devt.getCalendar()))); List<Integer> eventIds = eventIdsByHref.get(href); Integer eventId = (eventIds != null) ? eventIds.get(eventIds.size() - 1) : null; final ArrayList<EventInput> input = icalInput .fromICalendarFile(devt.getCalendar(), null); if (input.size() != 1) throw new WTException("iCal must contain one event"); final EventInput ei = input.get(0); if (eventId != null) { doEventDelete(con, eventId, false); } ei.event.setCalendarId(calendarId); ei.event.setHref(href); ei.event.setEtag(devt.geteTag()); doEventInputInsert(con, cache, ei); } } calDao.updateRemoteSyncById(con, calendarId, newLastSync, newSyncToken); DbUtils.commitQuietly(con); } catch (Exception ex) { DbUtils.rollbackQuietly(con); throw new WTException(ex, "Error importing iCalendar"); } } else { // Full update or partial computing hashes String newSyncToken = null; if (syncIsSupported) { // If supported, saves last sync-token issued by the server newSyncToken = dcal.getSyncToken(); } // Retrieves cards from DAV endpoint logger.debug("Querying CalDAV endpoint [{}]", params.url.toString()); List<DavCalendarEvent> devts = dav.listCalendarEvents(params.url.toString()); logger.debug("Returned {} items", devts.size()); // Handles data... try { Map<String, VEventHrefSync> syncByHref = null; if (full) { logger.debug("Cleaning up calendar [{}]", calendarId); doEventsDeleteByCalendar(con, calendarId, false); } else if (!full && !syncIsSupported) { // This hash-map is only needed when syncing using hashes EventDAO evtDao = EventDAO.getInstance(); syncByHref = evtDao.viewHrefSyncDataByCalendar(con, calendarId); } logger.debug("Processing results..."); // Define a simple map in order to check duplicates. // eg. SOGo passes same card twice :( HashSet<String> hrefs = new HashSet<>(); HashMap<String, OEvent> cache = new HashMap<>(); for (DavCalendarEvent devt : devts) { String href = PathUtils.getFileName(devt.getPath()); //String href = devt.getPath(); String etag = devt.geteTag(); if (logger.isTraceEnabled()) logger.trace("{}", ICalendarUtils.print(ICalendarUtils.getVEvent(devt.getCalendar()))); if (hrefs.contains(href)) { logger.trace("Card duplicated. Skipped! [{}]", href); continue; } boolean skip = false; Integer matchingEventId = null; if (syncByHref != null) { // Only if... (!full && !syncIsSupported) see above! //String prodId = ICalendarUtils.buildProdId(ManagerUtils.getProductName()); //String hash = DigestUtils.md5Hex(new ICalendarOutput(prodId, true).write(devt.getCalendar())); String hash = DigestUtils .md5Hex(ICalendarUtils.getVEvent(devt.getCalendar()).toString()); VEventHrefSync hrefSync = syncByHref.remove(href); if (hrefSync != null) { // Href found -> maybe updated item if (!StringUtils.equals(hrefSync.getEtag(), hash)) { matchingEventId = hrefSync.getEventId(); etag = hash; logger.trace("Event updated [{}, {}]", href, hash); } else { skip = true; logger.trace("Event not modified [{}, {}]", href, hash); } } else { // Href not found -> added item logger.trace("Event newly added [{}]", href); etag = hash; } } if (!skip) { final ArrayList<EventInput> input = icalInput .fromICalendarFile(devt.getCalendar(), null); if (input.size() != 1) throw new WTException("iCal must contain one event"); final EventInput ei = input.get(0); ei.event.setCalendarId(calendarId); ei.event.setHref(href); ei.event.setEtag(etag); if (matchingEventId == null) { doEventInputInsert(con, cache, ei); } else { ei.event.setEventId(matchingEventId); boolean updated = doEventInputUpdate(con, cache, ei); if (!updated) throw new WTException("Event not found [{}]", ei.event.getEventId()); } } hrefs.add(href); // Marks as processed! } if (syncByHref != null) { // Only if... (!full && !syncIsSupported) see above! // Remaining hrefs -> deleted items for (VEventHrefSync hrefSync : syncByHref.values()) { logger.trace("Event deleted [{}]", hrefSync.getHref()); doEventDelete(con, hrefSync.getEventId(), false); } } calDao.updateRemoteSyncById(con, calendarId, newLastSync, newSyncToken); DbUtils.commitQuietly(con); } catch (Exception ex) { DbUtils.rollbackQuietly(con); throw new WTException(ex, "Error importing iCalendar"); } } } catch (DavException ex) { throw new WTException(ex, "CalDAV error"); } } } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); pendingRemoteCalendarSyncs.remove(PENDING_KEY); } } private <T> T calculateFirstRecurringInstance(Connection con, RecurringInstanceMapper<T> instanceMapper, DateTimeZone userTimezone) throws WTException { List<T> instances = calculateRecurringInstances(con, instanceMapper, null, null, userTimezone, 1); return instances.isEmpty() ? null : instances.get(0); } private <T> List<T> calculateRecurringInstances(Connection con, RecurringInstanceMapper<T> instanceMapper, DateTime fromDate, DateTime toDate, DateTimeZone userTimezone) throws WTException { return calculateRecurringInstances(con, instanceMapper, fromDate, toDate, userTimezone, -1); } private Set<LocalDate> doGetExcludedDates(Connection con, int eventId, int recurrenceId) { /* RecurrenceBrokenDAO recbDao = RecurrenceBrokenDAO.getInstance(); LinkedHashSet<LocalDate> exdates = new LinkedHashSet(); List<ORecurrenceBroken> obrecs = recbDao.selectByEventRecurrence(con, eventId, recurrenceId); for (ORecurrenceBroken obrec : obrecs) { exdates.add(obrec.getEventDate()); } return exdates; */ RecurrenceBrokenDAO recbDao = RecurrenceBrokenDAO.getInstance(); Map<LocalDate, ORecurrenceBroken> obrecs = recbDao.selectByEventRecurrence(con, eventId, recurrenceId); return obrecs.keySet(); } private <T> List<T> calculateRecurringInstances(Connection con, RecurringInstanceMapper<T> instanceMapper, DateTime rangeFrom, DateTime rangeTo, DateTimeZone userTimezone, int limit) throws WTException { RecurrenceDAO recDao = RecurrenceDAO.getInstance(); RecurrenceBrokenDAO recbDao = RecurrenceBrokenDAO.getInstance(); ArrayList<T> instances = new ArrayList<>(); int eventId = instanceMapper.getEventId(); DateTime eventStart = instanceMapper.getEventStartDate(); DateTime eventEnd = instanceMapper.getEventEndDate(); DateTimeZone eventTimezone = instanceMapper.getEventTimezone(); LocalTime eventStartTime = eventStart.withZone(eventTimezone).toLocalTime(); LocalTime eventEndTime = eventEnd.withZone(eventTimezone).toLocalTime(); try { // Retrieves reccurence and broken dates (if any) ORecurrence orec = recDao.selectByEvent(con, eventId); if (orec == null) { logger.warn("Unable to retrieve recurrence for event [{}]", eventId); } else { if (rangeFrom == null) rangeFrom = orec.getStartDate(); if (rangeTo == null) rangeTo = orec.getStartDate().plusYears(1); Recur recur = orec.getRecur(); if (recur == null) throw new WTException("Unable to parse rrule [{}]", orec.getRule()); Map<LocalDate, ORecurrenceBroken> obrecs = recbDao.selectByEventRecurrence(con, eventId, orec.getRecurrenceId()); DateList dates = ICal4jUtils.calculateRecurrenceSet(recur, orec.getStartDate(), eventTimezone, rangeFrom, rangeTo, limit); Iterator it = dates.iterator(); while (it.hasNext()) { net.fortuna.ical4j.model.Date dt = (net.fortuna.ical4j.model.Date) it.next(); LocalDate recurringDate = ICal4jUtils.toJodaLocalDate(dt, eventTimezone); if (obrecs.containsKey(recurringDate)) continue; // Skip broken date... DateTime start = recurringDate.toDateTime(eventStartTime, eventTimezone).withZone(userTimezone); DateTime end = recurringDate.toDateTime(eventEndTime, eventTimezone).withZone(userTimezone); String key = EventKey.buildKey(eventId, eventId, recurringDate); instances.add(instanceMapper.createInstance(key, start, end)); } } } catch (DAOException ex) { throw wrapException(ex); } return instances; } private <T> List<T> calculateRecurringInstances_OLD(Connection con, RecurringInstanceMapper<T> instanceMapper, DateTime fromDate, DateTime toDate, DateTimeZone userTimezone, int limit) throws WTException { RecurrenceDAO recDao = RecurrenceDAO.getInstance(); RecurrenceBrokenDAO recbDao = RecurrenceBrokenDAO.getInstance(); ArrayList<T> instances = new ArrayList<>(); int eventId = instanceMapper.getEventId(); DateTime eventStartDate = instanceMapper.getEventStartDate(); DateTime eventEndDate = instanceMapper.getEventEndDate(); // Retrieves reccurence and broken dates (if any) ORecurrence orec = recDao.selectByEvent(con, eventId); if (orec == null) { logger.warn("Unable to retrieve recurrence for event [{}]", eventId); } else { if (fromDate == null) fromDate = orec.getStartDate(); if (toDate == null) toDate = orec.getStartDate().plusYears(1); /* List<ORecurrenceBroken> obrecs = recbDao.selectByEventRecurrence(con, eventId, orec.getRecurrenceId()); //TODO: ritornare direttamente l'hashmap da jooq // Builds a hashset of broken dates for increasing performances HashMap<String, ORecurrenceBroken> brokenDates = new HashMap<>(); for (ORecurrenceBroken obrec : obrecs) { brokenDates.put(obrec.getEventDate().toString(), obrec); } */ Map<LocalDate, ORecurrenceBroken> obrecs = recbDao.selectByEventRecurrence(con, eventId, orec.getRecurrenceId()); HashSet<String> brokenDates = new HashSet<>(); for (LocalDate ld : obrecs.keySet()) { brokenDates.add(ld.toString()); } try { // Calculate event length in order to generate events like original one int eventDays = CalendarUtils.calculateLengthInDays(eventStartDate, eventEndDate); RRule rr = new RRule(orec.getRule()); // Calcutate recurrence set for required dates range PeriodList periods = ICal4jUtils.calculateRecurrenceSet(eventStartDate, eventEndDate, orec.getStartDate(), rr, fromDate, toDate, userTimezone); // Recurrence start is useful to skip undesired dates at beginning. // If event does not starts at recurrence real beginning (eg. event // start on MO but first recurrence begin on WE), ical4j lib includes // those dates in calculated recurrence set, as stated in RFC // (http://tools.ietf.org/search/rfc5545#section-3.8.5.3). LocalDate rrStart = ICal4jUtils .calculateRecurrenceStart(orec.getStartDate(), rr.getRecur(), userTimezone).toLocalDate(); //LocalDate rrStart = ICal4jUtils.calculateRecurrenceStart(eventStartDate, rr.getRecur(), userTimezone).toLocalDate(); //TODO: valutare se salvare la data gi aggiornata LocalDate rrEnd = orec.getUntilDate().toLocalDate(); // Iterates returned recurring periods and builds cloned events... int count = -1; for (net.fortuna.ical4j.model.Period per : (Iterable<net.fortuna.ical4j.model.Period>) periods) { count++; if ((limit != -1) && (count > limit)) break; final LocalDate perStart = ICal4jUtils.toJodaDateTime(per.getStart()).toLocalDate(); final LocalDate perEnd = ICal4jUtils.toJodaDateTime(per.getEnd()).toLocalDate(); if (brokenDates.contains(perStart.toString())) continue; // Skip broken dates... if ((perStart.compareTo(rrStart) >= 0) && (perEnd.compareTo(rrEnd) <= 0)) { // Skip unwanted dates at beginning final DateTime newStart = eventStartDate.withDate(perStart); final DateTime newEnd = eventEndDate.withDate(newStart.plusDays(eventDays).toLocalDate()); final String key = EventKey.buildKey(eventId, eventId, perStart); instances.add(instanceMapper.createInstance(key, newStart, newEnd)); } } } catch (DAOException ex) { throw wrapException(ex); } catch (ParseException ex) { throw new WTException(ex, "Unable to parse rrule"); } } return instances; } /* private List<VVEventInstance> calculateRecurringInstances(Connection con, VVEvent event, DateTime fromDate, DateTime toDate, DateTimeZone userTimezone) throws WTException { return calculateRecurringInstances(con, event, fromDate, toDate, userTimezone, -1); } private List<VVEventInstance> calculateRecurringInstances(Connection con, VVEvent event, DateTime fromDate, DateTime toDate, DateTimeZone userTimezone, int limit) throws WTException { RecurrenceDAO recDao = RecurrenceDAO.getInstance(); RecurrenceBrokenDAO recbDao = RecurrenceBrokenDAO.getInstance(); if (event.getRecurrenceId() == null) throw new WTException("Specified event [{}] does not have a recurrence set", event.getEventId()); // Retrieves reccurence and broken dates (if any) ORecurrence orec = recDao.select(con, event.getRecurrenceId()); if (orec == null) throw new WTException("Unable to retrieve recurrence [{}]", event.getRecurrenceId()); List<ORecurrenceBroken> obrecs = recbDao.selectByEventRecurrence(con, event.getEventId(), event.getRecurrenceId()); if (fromDate == null) fromDate = orec.getStartDate(); if (toDate == null) toDate = orec.getStartDate().plusYears(5); //TODO: ritornare direttamente l'hashmap da jooq // Builds a hashset of broken dates for increasing performances HashMap<String, ORecurrenceBroken> brokenDates = new HashMap<>(); for(ORecurrenceBroken obrec : obrecs) { brokenDates.put(obrec.getEventDate().toString(), obrec); } // If not present, updates rrule if (StringUtils.isBlank(orec.getRule())) { orec.setRule(orec.buildRRule(DateTimeZone.UTC).getValue()); recDao.updateRRule(con, orec.getRecurrenceId(), orec.getRule()); } try { ArrayList<VVEventInstance> instances = new ArrayList<>(); // Calculate event length in order to generate events like original one int eventDays = calculateEventLengthInDays(event); RRule rr = new RRule(orec.getRule()); // Calcutate recurrence set for required dates range PeriodList periods = ICal4jUtils.calculateRecurrenceSet(event.getStartDate(), event.getEndDate(), orec.getStartDate(), rr, fromDate, toDate, userTimezone); // Recurrence start is useful to skip undesired dates at beginning. // If event does not starts at recurrence real beginning (eg. event // start on MO but first recurrence begin on WE), ical4j lib includes // those dates in calculated recurrence set, as stated in RFC // (http://tools.ietf.org/search/rfc5545#section-3.8.5.3). LocalDate rrStart = ICal4jUtils.calculateRecurrenceStart(event.getStartDate(), rr.getRecur(), userTimezone).toLocalDate(); //TODO: valutare se salvare la data gi aggiornata LocalDate rrEnd = orec.getUntilDate().toLocalDate(); // Iterates returned recurring periods and builds cloned events... int count = -1; final Cloner cloner = Cloner.standard(); for (net.fortuna.ical4j.model.Period per : (Iterable<net.fortuna.ical4j.model.Period>) periods) { count++; if ((limit != -1) && (count > limit)) break; final LocalDate perStart = ICal4jUtils.toJodaDateTime(per.getStart()).toLocalDate(); final LocalDate perEnd = ICal4jUtils.toJodaDateTime(per.getEnd()).toLocalDate(); if (brokenDates.containsKey(perStart.toString())) continue; // Skip broken dates... if ((perStart.compareTo(rrStart) >= 0) && (perEnd.compareTo(rrEnd) <= 0)) { // Skip unwanted dates at beginning final DateTime newStart = event.getStartDate().withDate(perStart); final DateTime newEnd = event.getEndDate().withDate(newStart.plusDays(eventDays).toLocalDate()); // Generate cloned event like original one VVEvent clone = cloner.deepClone(event); clone.setStartDate(newStart); clone.setEndDate(newEnd); instances.add(new VVEventInstance(EventKey.buildKey(event.getEventId(), event.getEventId(), perStart), clone)); } } return instances; } catch(DAOException ex) { throw wrapException(ex); } catch(ParseException ex) { throw new WTException(ex, "Unable to parse rrule"); } } */ public static String buildEventPublicUrl(String publicBaseUrl, String eventPublicId) { String s = PublicService.PUBPATH_CONTEXT_EVENT + "/" + eventPublicId; return PathUtils.concatPaths(publicBaseUrl, s); } public static String buildEventReplyPublicUrl(String publicBaseUrl, String eventPublicId, String attendeePublicId, String resp) { String s = PublicService.PUBPATH_CONTEXT_EVENT + "/" + eventPublicId + "/" + PublicService.EventUrlPath.TOKEN_REPLY + "?aid=" + attendeePublicId + "&resp=" + resp; return PathUtils.concatPaths(publicBaseUrl, s); } private void fillExportMapDates(HashMap<String, Object> map, DateTimeZone timezone, VVEventInstance sei) throws Exception { DateTime startDt = sei.getStartDate().withZone(timezone); map.put("startDate", startDt); map.put("startTime", startDt); DateTime endDt = sei.getEndDate().withZone(timezone); map.put("endDate", endDt); map.put("endTime", endDt); map.put("timezone", sei.getTimezone()); map.put("duration", Minutes.minutesBetween(sei.getEndDate(), sei.getStartDate()).size()); } private void fillExportMapBasic(HashMap<String, Object> map, CoreManager coreMgr, Connection con, VVEvent event) throws Exception { map.put("eventId", event.getEventId()); map.put("title", event.getTitle()); map.put("description", event.getDescription()); if (event.getActivityId() != null) { ActivityDAO actDao = ActivityDAO.getInstance(); OActivity activity = actDao.select(con, event.getActivityId()); if (activity == null) throw new WTException("Activity [{0}] not found", event.getActivityId()); map.put("activityId", activity.getActivityId()); map.put("activityDescription", activity.getDescription()); map.put("activityExternalId", activity.getExternalId()); } if (event.getMasterDataId() != null) { MasterData md = coreMgr.getMasterData(event.getMasterDataId()); map.put("masterDataId", md.getMasterDataId()); map.put("masterDataDescription", md.getDescription()); map.put("customerId", md.getMasterDataId()); map.put("customerDescription", md.getDescription()); } if (event.getCausalId() != null) { CausalDAO cauDao = CausalDAO.getInstance(); OCausal causal = cauDao.select(con, event.getCausalId()); if (causal == null) throw new WTException("Causal [{0}] not found", event.getCausalId()); map.put("causalId", causal.getCausalId()); map.put("causalDescription", causal.getDescription()); map.put("causalExternalId", causal.getExternalId()); } } private Calendar doCalendarGet(Connection con, int calendarId) throws DAOException { CalendarDAO calDao = CalendarDAO.getInstance(); return ManagerUtils.createCalendar(calDao.selectById(con, calendarId)); } private UserProfileId doCalendarGetOwner(int calendarId) throws WTException { OCalendarOwnerInfo ocoi = doCalendarGetOwnerInfo(calendarId); return (ocoi == null) ? null : new UserProfileId(ocoi.getDomainId(), ocoi.getUserId()); } private OCalendarOwnerInfo doCalendarGetOwnerInfo(int calendarId) throws WTException { Connection con = null; try { con = WT.getConnection(SERVICE_ID); return doCalendarGetOwnerInfo(con, calendarId); } catch (SQLException | DAOException ex) { throw wrapException(ex); } finally { DbUtils.closeQuietly(con); } } private OCalendarOwnerInfo doCalendarGetOwnerInfo(Connection con, int calendarId) throws DAOException { CalendarDAO calDao = CalendarDAO.getInstance(); return calDao.selectOwnerInfoById(con, calendarId); } private Calendar doCalendarInsert(Connection con, Calendar cal) throws DAOException { CalendarDAO calDao = CalendarDAO.getInstance(); OCalendar ocal = ManagerUtils.createOCalendar(cal); ocal.setCalendarId(calDao.getSequence(con).intValue()); fillOCalendarWithDefaults(ocal); if (ocal.getIsDefault()) calDao.resetIsDefaultByProfile(con, ocal.getDomainId(), ocal.getUserId()); calDao.insert(con, ocal); return ManagerUtils.createCalendar(ocal); } private boolean doCalendarUpdate(Connection con, Calendar cal) throws DAOException { CalendarDAO calDao = CalendarDAO.getInstance(); OCalendar ocal = ManagerUtils.createOCalendar(cal); fillOCalendarWithDefaults(ocal); if (ocal.getIsDefault()) calDao.resetIsDefaultByProfile(con, ocal.getDomainId(), ocal.getUserId()); return calDao.update(con, ocal) == 1; } private EventObject doEventObjectPrepare(Connection con, VEventObject vobj, EventObjectOutputType outputType) throws WTException { if (EventObjectOutputType.STAT.equals(outputType)) { return ManagerUtils.fillEventCalObject(new EventObject(), vobj); } else { RecurrenceDAO recDao = RecurrenceDAO.getInstance(); EventAttendeeDAO attDao = EventAttendeeDAO.getInstance(); Event event = ManagerUtils.fillEvent(new Event(), vobj); if (vobj.getRecurrenceId() != null) { ORecurrence orec = recDao.select(con, vobj.getRecurrenceId()); if (orec == null) throw new WTException("Unable to get recurrence [{}]", vobj.getRecurrenceId()); Set<LocalDate> excludedDates = doGetExcludedDates(con, event.getEventId(), vobj.getRecurrenceId()); event.setRecurrence(orec.getRule(), orec.getLocalStartDate(event.getDateTimeZone()), excludedDates); } if (vobj.hasAttendees()) { List<OEventAttendee> oatts = attDao.selectByEvent(con, event.getEventId()); event.setAttendees(ManagerUtils.createEventAttendeeList(oatts)); } if (EventObjectOutputType.ICALENDAR.equals(outputType)) { EventObjectWithICalendar eco = ManagerUtils.fillEventCalObject(new EventObjectWithICalendar(), vobj); ICalendarOutput out = new ICalendarOutput( ICalendarUtils.buildProdId(ManagerUtils.getProductName())); //TODO: add support to excluded dates net.fortuna.ical4j.model.Calendar iCal = out.toCalendar(event); if (vobj.getHasIcalendar()) { //TODO: in order to be fully compliant, merge generated vcard with the original one in db table! } try { eco.setIcalendar(out.write(iCal)); } catch (IOException ex) { throw new WTException(ex, "Unable to write iCalendar"); } return eco; } else { EventObjectWithBean eco = ManagerUtils.fillEventCalObject(new EventObjectWithBean(), vobj); eco.setEvent(event); return eco; } } } private Integer doEventGetId(Connection con, Collection<Integer> calendarIdMustBeIn, String publicUid) throws WTException { EventDAO evtDao = EventDAO.getInstance(); List<Integer> ids = null; if ((calendarIdMustBeIn == null) || calendarIdMustBeIn.isEmpty()) { // This kind of lookup is suitable for calls executed by admin from public service ids = evtDao.selectAliveIdsByPublicUid(con, publicUid); } else { logger.trace("Looking for publicId in restricted set of calendars..."); ids = evtDao.selectAliveIdsByCalendarsPublicUid(con, calendarIdMustBeIn, publicUid); } if (ids.isEmpty()) return null; if (ids.size() > 1) logger.warn("Multiple events found for public id [{}]", publicUid); return ids.get(0); } private Event doEventGet(Connection con, int eventId, boolean attachments, boolean forZPushFix) throws DAOException, WTException { EventDAO evtDao = EventDAO.getInstance(); EventAttendeeDAO atteDao = EventAttendeeDAO.getInstance(); RecurrenceDAO recDao = RecurrenceDAO.getInstance(); EventAttachmentDAO attchDao = EventAttachmentDAO.getInstance(); OEvent oevt = forZPushFix ? evtDao.selectById(con, eventId) : evtDao.selectAliveById(con, eventId); if (oevt == null) return null; Event evt = ManagerUtils.createEvent(oevt); if (oevt.getRecurrenceId() != null) { ORecurrence orec = recDao.select(con, oevt.getRecurrenceId()); if (orec == null) throw new WTException("Unable to get recurrence [{}]", oevt.getRecurrenceId()); Set<LocalDate> excludedDates = doGetExcludedDates(con, oevt.getEventId(), oevt.getRecurrenceId()); evt.setRecurrence(orec.getRule(), orec.getLocalStartDate(evt.getDateTimeZone()), excludedDates); } List<OEventAttendee> oattes = atteDao.selectByEvent(con, eventId); //evt.setAttendees(ManagerUtils.createEventAttendeeList(oattes)); if (!oattes.isEmpty()) { evt.setAttendees(ManagerUtils.createEventAttendeeList(oattes)); } // Fill attachments (if necessary) if (attachments) { List<OEventAttachment> oattchs = attchDao.selectByEvent(con, eventId); evt.setAttachments(ManagerUtils.createEventAttachmentList(oattchs)); } return evt; } private Event doEventGet(Connection con, GetEventScope scope, String publicUid) throws WTException { ArrayList<Integer> ids = new ArrayList<>(); if (scope.equals(GetEventScope.PERSONAL) || scope.equals(GetEventScope.PERSONAL_AND_INCOMING)) { Integer eventId = doEventGetId(con, listCalendarIds(), publicUid); if (eventId != null) ids.add(eventId); } if (scope.equals(GetEventScope.INCOMING) || scope.equals(GetEventScope.PERSONAL_AND_INCOMING)) { Integer eventId = doEventGetId(con, listIncomingCalendarIds(), publicUid); if (eventId != null) ids.add(eventId); } if (ids.isEmpty()) return null; // Filled array could contains more than one result, eg. in case of // invitation between two users of the same domain where the target // calendar of one user is shared to the other. // Returning the first result is the most appropriated action because // personal elements are returned first. //if (ids.size() > 1) throw new WTException("Multiple events found for public id [{}]", publicUid); return doEventGet(con, ids.get(0), false, false); } private void doEventMasterUpdateAndCommit(Connection con, Event event, boolean notifyAttendees) throws IOException, WTException { EventDAO evtDao = EventDAO.getInstance(); OEvent oevtOrig = evtDao.selectById(con, event.getEventId()); if (oevtOrig == null) throw new WTException("Unable get original event [{}]", event.getEventId()); // 1 - Updates event with new data doEventUpdate(con, oevtOrig, event, true, true, true, true); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(event.getEventId())); Event eventDump = getEvent(event.getEventId()); if (eventDump == null) throw new WTException("Missing eventDump"); if (eventDump.hasRecurrence()) { // Gets the first valid instance in case of recurring event eventDump = calculateFirstRecurringInstance(con, new EventMapper(eventDump), eventDump.getDateTimeZone()); } // Notify last modification List<RecipientTuple> nmRcpts = getModificationRecipients(eventDump.getCalendarId(), Crud.UPDATE); if (!nmRcpts.isEmpty()) notifyForEventModification(RunContext.getRunProfileId(), nmRcpts, eventDump.getFootprint(), Crud.UPDATE); // Notify attendees if (notifyAttendees) { List<RecipientTuple> attRcpts = getInvitationRecipients(getTargetProfileId(), eventDump, Crud.UPDATE); if (!attRcpts.isEmpty()) notifyForInvitation(getTargetProfileId(), attRcpts, eventDump, Crud.UPDATE); } } private EventInstance doEventInstanceGet(Connection con, int eventId, LocalDate date, boolean attachments) throws WTException { EventDAO evtDao = EventDAO.getInstance(); RecurrenceDAO recDao = RecurrenceDAO.getInstance(); EventAttendeeDAO atteDao = EventAttendeeDAO.getInstance(); EventAttachmentDAO attchDao = EventAttachmentDAO.getInstance(); OEvent oevt = evtDao.selectById(con, eventId); if (oevt == null) throw new WTException("Unable to get event [{}]", eventId); EventInstance ei = ManagerUtils.fillEvent(new EventInstance(), oevt); // Fill recurrence (if necessary) ORecurrence orec = recDao.select(con, oevt.getRecurrenceId()); if (orec != null) { if (date == null) throw new WTException("Date is required for recurring events [{}]", eventId); Set<LocalDate> excludedDates = doGetExcludedDates(con, oevt.getEventId(), oevt.getRecurrenceId()); int eventDays = CalendarUtils.calculateLengthInDays(oevt.getStartDate(), oevt.getEndDate()); ei.setStartDate(ei.getStartDate().withDate(date)); ei.setEndDate(ei.getEndDate().withDate(ei.getStartDate().plusDays(eventDays).toLocalDate())); ei.setRecurrence(orec.getRule(), orec.getLocalStartDate(ei.getDateTimeZone()), excludedDates); ei.setKey(EventKey.buildKey(eventId, eventId, date)); ei.setRecurInfo(Event.RecurInfo.RECURRING); } else { Integer seriesEventId = evtDao.selectAliveSeriesEventIdById(con, eventId); ei.setKey(EventKey.buildKey(eventId, seriesEventId)); ei.setRecurInfo((seriesEventId == null) ? Event.RecurInfo.NONE : Event.RecurInfo.BROKEN); } // Fill attendees List<OEventAttendee> oattes = atteDao.selectByEvent(con, eventId); ei.setAttendees(ManagerUtils.createEventAttendeeList(oattes)); // Fill attachments (if necessary) if (attachments) { List<OEventAttachment> oattchs = attchDao.selectByEvent(con, eventId); ei.setAttachments(ManagerUtils.createEventAttachmentList(oattchs)); } return ei; } private void doEventInstanceUpdateAndCommit(Connection con, UpdateEventTarget target, EventKey eventKey, Event event, boolean notifyAttendees) throws IOException, WTException { EventDAO evtDao = EventDAO.getInstance(); RecurrenceDAO recDao = RecurrenceDAO.getInstance(); //if (con.getAutoCommit()) throw new WTException("This method should not be called in this way. Connection must not be in auto-commit mode!"); OEventInfo einfo = evtDao.selectOEventInfoById(con, event.getEventId()); if (einfo == null) throw new WTException("Unable to retrieve event info [{}]", event.getEventId()); OEvent oevtOrig = evtDao.selectById(con, einfo.getEventId()); if (oevtOrig == null) throw new WTException("Unable get original event [{}]", einfo.getEventId()); Event eventDump = null; if (einfo.isRecurring()) { if (UpdateEventTarget.THIS_INSTANCE.equals(target)) { // Changes are valid for this specific instance // 1 - Inserts new broken event (attendees and rr are not supported here) EventInsertResult insert = doEventInsert(con, event, null, false, false, false, false); // 2 - Inserts new broken record (marks recurring event) on modified date doRecurrenceExcludeDate(con, oevtOrig, eventKey.instanceDate, insert.event.getEventId()); // 3 - Updates revision of original event evtDao.updateRevision(con, oevtOrig.getEventId(), BaseDAO.createRevisionTimestamp()); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(oevtOrig.getEventId())); writeLog("EVENT_INSERT", String.valueOf(insert.event.getEventId())); //core.addServiceSuggestionEntry(SERVICE_ID, SUGGESTION_EVENT_TITLE, insert.event.getTitle()); //core.addServiceSuggestionEntry(SERVICE_ID, SUGGESTION_EVENT_LOCATION, insert.event.getLocation()); eventDump = getEvent(einfo.getEventId()); } else if (UpdateEventTarget.SINCE_INSTANCE.equals(target)) { // Changes are valid from this instance onward // 1 - Resize original recurrence (sets until date at the day before date) ORecurrence orec = recDao.select(con, einfo.getRecurrenceId()); if (orec == null) throw new WTException("Unable to get master event's recurrence [{}]", einfo.getRecurrenceId()); int oldDaysBetween = CalendarUtils.calculateLengthInDays(event.getStartDate(), event.getEndDate()); Recur oldRecur = orec.getRecur(); // Dump old recur! LocalTime untilTime = einfo.getAllDay() ? DateTimeUtils.TIME_AT_STARTOFDAY : einfo.getStartDate().withZone(einfo.getDateTimeZone()).toLocalTime(); orec.updateUntilDate(eventKey.instanceDate.minusDays(1), untilTime, einfo.getDateTimeZone()); recDao.update(con, orec); // 2 - Updates revision of original event evtDao.updateRevision(con, einfo.getEventId(), BaseDAO.createRevisionTimestamp()); // 3 - Insert new event recalculating start/end from rec. start preserving days duration event.setStartDate(event.getStartDate().withDate(eventKey.instanceDate)); event.setEndDate(event.getEndDate().withDate(eventKey.instanceDate.plusDays(oldDaysBetween))); // We cannot keep original count, it would be wrong... so convert it to an until date! if (ICal4jUtils.recurHasCount(oldRecur)) { DateTime oldUntilReal = ICal4jUtils.calculateRecurrenceEnd(oldRecur, oevtOrig.getStartDate(), oevtOrig.getDateTimezone()); ICal4jUtils.setRecurUntilDate(oldRecur, oldUntilReal); } event.setRecurrence(oldRecur.toString(), eventKey.instanceDate, null); EventInsertResult insert = doEventInsert(con, event, null, true, false, false, false); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(einfo.getEventId())); writeLog("EVENT_INSERT", String.valueOf(insert.event.getEventId())); // TODO: eventually add support to clone attendees in the newly inserted event and so sending invitation emails eventDump = getEvent(einfo.getEventId()); } else if (UpdateEventTarget.ALL_SERIES.equals(target)) { // Changes are valid for all the instances (whole recurrence) // We need to restore original dates because current start/end refers // to instance and not to the master event. event.setStartDate(event.getStartDate().withDate(oevtOrig.getStartDate().toLocalDate())); event.setEndDate(event.getEndDate().withDate(oevtOrig.getEndDate().toLocalDate())); // 1 - Updates event with new data doEventUpdate(con, oevtOrig, event, true, false, true, true); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(einfo.getEventId())); eventDump = getEvent(einfo.getEventId()); } } else if (einfo.isBroken()) { // 1 - Updates broken event (follow eventId) with new data doEventUpdate(con, oevtOrig, event, false, false, true, true); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(einfo.getEventId())); //core.addServiceSuggestionEntry(SERVICE_ID, SUGGESTION_EVENT_TITLE, event.getTitle()); //core.addServiceSuggestionEntry(SERVICE_ID, SUGGESTION_EVENT_LOCATION, event.getLocation()); eventDump = getEvent(einfo.getEventId()); } else { // 1 - Updates this event with new data doEventUpdate(con, oevtOrig, event, true, false, true, true); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(oevtOrig.getEventId())); //core.addServiceSuggestionEntry(SERVICE_ID, SUGGESTION_EVENT_TITLE, event.getTitle()); //core.addServiceSuggestionEntry(SERVICE_ID, SUGGESTION_EVENT_LOCATION, event.getLocation()); eventDump = getEvent(einfo.getEventId()); } if (eventDump == null) throw new WTException("Missing eventDump"); if (eventDump.hasRecurrence()) { // Gets the first valid instance in case of recurring event eventDump = calculateFirstRecurringInstance(con, new EventMapper(eventDump), eventDump.getDateTimeZone()); } // Notify last modification List<RecipientTuple> nmRcpts = getModificationRecipients(eventDump.getCalendarId(), Crud.UPDATE); if (!nmRcpts.isEmpty()) notifyForEventModification(RunContext.getRunProfileId(), nmRcpts, eventDump.getFootprint(), Crud.UPDATE); // Notify attendees if (notifyAttendees) { List<RecipientTuple> attRcpts = getInvitationRecipients(getTargetProfileId(), eventDump, Crud.UPDATE); if (!attRcpts.isEmpty()) notifyForInvitation(getTargetProfileId(), attRcpts, eventDump, Crud.UPDATE); } } private void doEventInstanceDeleteAndCommit(Connection con, UpdateEventTarget target, EventKey eventKey, boolean notifyAttendees) throws WTException { EventDAO evtDao = EventDAO.getInstance(); RecurrenceDAO recDao = RecurrenceDAO.getInstance(); //if (con.getAutoCommit()) throw new WTException("This method should not be called in this way. Connection must not be in auto-commit mode!"); OEventInfo einfo = evtDao.selectOEventInfoById(con, eventKey.eventId); if (einfo == null) throw new WTException("Unable to retrieve event info [{}]", eventKey.eventId); Event eventDump = null; if (einfo.isRecurring()) { if (UpdateEventTarget.THIS_INSTANCE.equals(target)) { // Changes are valid for this specific instance // 1 - Inserts new broken record (without new broken event) on deleted date doRecurrenceExcludeDate(con, einfo.getEventId(), einfo.getRecurrenceId(), eventKey.instanceDate, null); // 2 - Updates revision of this event evtDao.updateRevision(con, einfo.getEventId(), BaseDAO.createRevisionTimestamp()); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(einfo.getEventId())); eventDump = getEvent(einfo.getEventId()); } else if (UpdateEventTarget.SINCE_INSTANCE.equals(target)) { // Changes are valid from this instance onward // 1 - Resize original recurrence (sets until date at the day before date) ORecurrence orec = recDao.select(con, einfo.getRecurrenceId()); if (orec == null) throw new WTException("Unable to get master event's recurrence [{}]", einfo.getRecurrenceId()); //orec.updateUntilDate(eventKey.instanceDate, einfo.getDateTimeZone()); LocalTime untilTime = einfo.getAllDay() ? DateTimeUtils.TIME_AT_STARTOFDAY : einfo.getStartDate().withZone(einfo.getDateTimeZone()).toLocalTime(); orec.updateUntilDate(eventKey.instanceDate, untilTime, einfo.getDateTimeZone()); recDao.update(con, orec); // 2 - Updates revision of this event evtDao.updateRevision(con, einfo.getEventId(), BaseDAO.createRevisionTimestamp()); DbUtils.commitQuietly(con); writeLog("EVENT_UPDATE", String.valueOf(einfo.getEventId())); eventDump = getEvent(einfo.getEventId()); } else if (UpdateEventTarget.ALL_SERIES.equals(target)) { // Changes are valid for all the instances (whole recurrence) eventDump = getEvent(einfo.getEventId()); // Save for later use! // 1 - logically delete this event doEventDelete(con, einfo.getEventId(), true); DbUtils.commitQuietly(con); writeLog("EVENT_DELETE", String.valueOf(einfo.getEventId())); } } else if (einfo.isBroken()) { eventDump = getEventInstance(eventKey); // Save for later use! // 1 - Logically delete this event (the broken) doEventDelete(con, einfo.getEventId(), true); // 2 - Updates revision of linked event evtDao.updateRevision(con, einfo.getLinkedEventId(), BaseDAO.createRevisionTimestamp()); DbUtils.commitQuietly(con); writeLog("EVENT_DELETE", String.valueOf(einfo.getEventId())); writeLog("EVENT_UPDATE", String.valueOf(einfo.getLinkedEventId())); } else { eventDump = getEventInstance(eventKey); // Save for later use! // 1 - logically delete this event doEventDelete(con, einfo.getEventId(), true); DbUtils.commitQuietly(con); writeLog("EVENT_DELETE", String.valueOf(einfo.getEventId())); } if (eventDump == null) throw new WTException("Missing eventDump"); if (eventDump.hasRecurrence()) { // Gets the first valid instance in case of recurring event eventDump = calculateFirstRecurringInstance(con, new EventMapper(eventDump), eventDump.getDateTimeZone()); } // Notify last modification List<RecipientTuple> nmRcpts = getModificationRecipients(eventDump.getCalendarId(), Crud.DELETE); if (!nmRcpts.isEmpty()) notifyForEventModification(RunContext.getRunProfileId(), nmRcpts, eventDump.getFootprint(), Crud.DELETE); // Notify attendees if (notifyAttendees) { List<RecipientTuple> attRcpts = getInvitationRecipients(getTargetProfileId(), eventDump, Crud.DELETE); if (!attRcpts.isEmpty()) notifyForInvitation(getTargetProfileId(), attRcpts, eventDump, Crud.DELETE); } } private void doEventInstanceRestoreAndCommit(Connection con, EventKey eventKey, boolean notifyAttendees) throws WTException { EventDAO evtDao = EventDAO.getInstance(); RecurrenceBrokenDAO rbkDao = RecurrenceBrokenDAO.getInstance(); //if (con.getAutoCommit()) throw new WTException("This method should not be called in this way. Connection must not be in auto-commit mode!"); OEventInfo einfo = evtDao.selectOEventInfoById(con, eventKey.eventId); if (einfo == null) throw new WTException("Unable to retrieve event info [{}]", eventKey.eventId); Event eventDump = null; if (einfo.isBroken()) { eventDump = getEventInstance(eventKey); // Save for later use! // 1 - Removes the broken record rbkDao.deleteByNewEvent(con, einfo.getEventId()); // 2 - Logically delete this event (the broken) doEventDelete(con, einfo.getEventId(), true); // 3 - updates revision of linked event evtDao.updateRevision(con, einfo.getLinkedEventId(), BaseDAO.createRevisionTimestamp()); DbUtils.commitQuietly(con); writeLog("EVENT_DELETE", String.valueOf(einfo.getEventId())); writeLog("EVENT_UPDATE", String.valueOf(einfo.getLinkedEventId())); // TODO: eventually add support to notify attendees of the linked event of date restoration } else { throw new WTException("Cannot restore an event that is not broken"); } if (eventDump == null) throw new WTException("Missing eventDump"); if (eventDump.hasRecurrence()) { // Gets the first valid instance in case of recurring event eventDump = calculateFirstRecurringInstance(con, new EventMapper(eventDump), eventDump.getDateTimeZone()); } // Notify last modification List<RecipientTuple> nmRcpts = getModificationRecipients(eventDump.getCalendarId(), Crud.DELETE); if (!nmRcpts.isEmpty()) notifyForEventModification(RunContext.getRunProfileId(), nmRcpts, eventDump.getFootprint(), Crud.DELETE); // Notify attendees if (notifyAttendees) { List<RecipientTuple> attRcpts = getInvitationRecipients(getTargetProfileId(), eventDump, Crud.DELETE); if (!attRcpts.isEmpty()) notifyForInvitation(getTargetProfileId(), attRcpts, eventDump, Crud.DELETE); } } private boolean doEventInputUpdate(Connection con, HashMap<String, OEvent> cache, EventInput input) throws DAOException, IOException { //TODO: Make this smart avoiding delete/insert! doEventDelete(con, input.event.getEventId(), false); doEventInputInsert(con, cache, input); return true; } private EventInsertResult doEventInputInsert(Connection con, HashMap<String, OEvent> cache, EventInput ei) throws DAOException, IOException { EventInsertResult insert = doEventInsert(con, ei.event, null, true, true, true, false); if (insert.recurrence != null) { // Cache recurring event for future use within broken references cache.put(insert.event.getPublicUid(), insert.event); } else { if (ei.addsExOnMaster != null) { if (cache.containsKey(ei.exRefersToPublicUid)) { final OEvent oevt = cache.get(ei.exRefersToPublicUid); doRecurrenceExcludeDate(con, oevt, ei.addsExOnMaster, insert.event.getEventId()); } } } return insert; } private EventInsertResult doEventInsert(Connection con, Event event, String rawICalendar, boolean processRecurrence, boolean processExcludedDates, boolean processAttendees, boolean processAttachments) throws DAOException, IOException { EventDAO evtDao = EventDAO.getInstance(); RecurrenceDAO recDao = RecurrenceDAO.getInstance(); EventAttendeeDAO attDao = EventAttendeeDAO.getInstance(); OEvent oevt = ManagerUtils.createOEvent(event); oevt.setEventId(evtDao.getSequence(con).intValue()); oevt.setRevisionStatus(EnumUtils.toSerializedName(Event.RevisionStatus.NEW)); fillOEventWithDefaults(oevt); oevt.ensureCoherence(); ORecurrence orec = null; if (processRecurrence && event.hasRecurrence()) { Recur recur = ICal4jUtils.parseRRule(event.getRecurrenceRule()); orec = new ORecurrence(); orec.set(recur, event.getRecurrenceStartDate(), event.getStartDate(), event.getEndDate(), event.getDateTimeZone()); orec.setRecurrenceId(recDao.getSequence(con).intValue()); recDao.insert(con, orec); } oevt.setRecurrenceId((orec != null) ? orec.getRecurrenceId() : null); evtDao.insert(con, oevt, BaseDAO.createRevisionTimestamp()); ArrayList<ORecurrenceBroken> obrks = null; if ((orec != null) && processExcludedDates && event.hasExcludedDates()) { obrks = new ArrayList<>(); for (LocalDate ld : event.getExcludedDates()) { obrks.add(doRecurrenceExcludeDate(con, oevt, ld)); } } if (!StringUtils.isBlank(rawICalendar)) { doEventICalendarInsert(con, oevt.getEventId(), rawICalendar); } ArrayList<OEventAttendee> oattes = null; if (processAttendees && (event.getAttendees() != null)) { oattes = new ArrayList<>(); for (EventAttendee att : event.getAttendees()) { if (!ManagerUtils.validateForInsert(att)) continue; OEventAttendee oatt = ManagerUtils.createOEventAttendee(att); oatt.setAttendeeId(IdentifierUtils.getUUID()); oatt.setEventId(oevt.getEventId()); attDao.insert(con, oatt); oattes.add(oatt); } } ArrayList<OEventAttachment> oattchs = null; if (processAttachments && (event.getAttachments() != null)) { oattchs = new ArrayList<>(); for (EventAttachment att : event.getAttachments()) { if (!(att instanceof EventAttachmentWithStream)) throw new IOException("Attachment stream not available [" + att.getAttachmentId() + "]"); oattchs.add(doEventAttachmentInsert(con, oevt.getEventId(), (EventAttachmentWithStream) att)); } } return new EventInsertResult(oevt, orec, obrks, oattes, oattchs); } private boolean doEventUpdate(Connection con, OEvent originalEvent, Event event, boolean processAttendees, boolean processAttachments) throws IOException, WTException { return doEventUpdate(con, originalEvent, event, false, false, processAttendees, processAttachments); } private boolean doEventUpdate(Connection con, OEvent originalEvent, Event event, boolean processRecurrence, boolean processExcludedDates, boolean processAttendees, boolean processAttachments) throws IOException, WTException { EventDAO evtDao = EventDAO.getInstance(); EventAttendeeDAO atteDao = EventAttendeeDAO.getInstance(); EventAttachmentDAO attchDao = EventAttachmentDAO.getInstance(); RecurrenceDAO recDao = RecurrenceDAO.getInstance(); RecurrenceBrokenDAO rbkDao = RecurrenceBrokenDAO.getInstance(); DateTime revision = BaseDAO.createRevisionTimestamp(); if (StringUtils.isBlank(event.getOrganizer())) { event.setOrganizer(ManagerUtils.buildOrganizer(getTargetProfileId())); // Make sure organizer is filled } ManagerUtils.fillOEvent(originalEvent, event); originalEvent.ensureCoherence(); if (processRecurrence) { ORecurrence orec = recDao.select(con, originalEvent.getRecurrenceId()); if (event.hasRecurrence() && (orec != null)) { // New event has recurrence and the old too Recur recur = ICal4jUtils.parseRRule(event.getRecurrenceRule()); boolean recurIsChanged = !ICal4jUtils.equals(recur, orec.getRecur()); // Updates recurrence orec.set(recur, event.getRecurrenceStartDate(), event.getStartDate(), event.getEndDate(), event.getDateTimeZone()); recDao.update(con, orec); // If rule is changed, cleanup stored exceptions (we lose any broken events restore information) if (recurIsChanged) { logger.debug("Recurrence rule is changed, cleaning previous broken dates..."); rbkDao.deleteByRecurrence(con, orec.getRecurrenceId()); } // Inserts broken records that exclude some dates if (processExcludedDates && event.hasExcludedDates()) { Set<LocalDate> exDates = doGetExcludedDates(con, originalEvent.getEventId(), originalEvent.getRecurrenceId()); for (LocalDate ld : event.getExcludedDates()) { if (exDates.contains(ld)) continue; doRecurrenceExcludeDate(con, originalEvent, ld); } } } else if (event.hasRecurrence() && (orec == null)) { // New event has recurrence but the old doesn't Recur recur = ICal4jUtils.parseRRule(event.getRecurrenceRule()); // Inserts recurrence orec = new ORecurrence(); orec.set(recur, null, event.getStartDate(), event.getEndDate(), event.getDateTimeZone()); orec.setRecurrenceId(recDao.getSequence(con).intValue()); recDao.insert(con, orec); originalEvent.setRecurrenceId(orec.getRecurrenceId()); // Inserts broken records that exclude some dates if (processExcludedDates && event.hasExcludedDates()) { for (LocalDate ld : event.getExcludedDates()) { doRecurrenceExcludeDate(con, originalEvent, ld); } } } else if (!event.hasRecurrence() && (orec != null)) { // New event doesn't have recurrence but the old does rbkDao.deleteByRecurrence(con, orec.getRecurrenceId()); recDao.deleteById(con, orec.getRecurrenceId()); originalEvent.setRecurrenceId(null); } } boolean ret = evtDao.update(con, originalEvent, revision, originalEvent.getStartDate().isAfterNow()) == 1; if (processAttendees && (event.getAttendees() != null)) { List<EventAttendee> oldAtts = ManagerUtils .createEventAttendeeList(atteDao.selectByEvent(con, originalEvent.getEventId())); CollectionChangeSet<EventAttendee> changeSet = LangUtils.getCollectionChanges(oldAtts, event.getAttendees()); for (EventAttendee att : changeSet.inserted) { if (!ManagerUtils.validateForInsert(att)) continue; final OEventAttendee oatt = ManagerUtils.createOEventAttendee(att); oatt.setAttendeeId(IdentifierUtils.getUUID()); oatt.setEventId(originalEvent.getEventId()); atteDao.insert(con, oatt); } for (EventAttendee att : changeSet.updated) { if (!ManagerUtils.validateForUpdate(att)) continue; final OEventAttendee oatt = ManagerUtils.createOEventAttendee(att); atteDao.update(con, oatt); } for (EventAttendee att : changeSet.deleted) { atteDao.delete(con, att.getAttendeeId()); } } if (processAttachments && (event.getAttachments() != null)) { List<EventAttachment> oldAttchs = ManagerUtils .createEventAttachmentList(attchDao.selectByEvent(con, event.getEventId())); CollectionChangeSet<EventAttachment> changeSet = LangUtils.getCollectionChanges(oldAttchs, event.getAttachments()); for (EventAttachment att : changeSet.inserted) { if (!(att instanceof EventAttachmentWithStream)) throw new IOException("Attachment stream not available [" + att.getAttachmentId() + "]"); doEventAttachmentInsert(con, originalEvent.getEventId(), (EventAttachmentWithStream) att); } for (EventAttachment att : changeSet.updated) { if (!(att instanceof EventAttachmentWithStream)) continue; doEventAttachmentUpdate(con, (EventAttachmentWithStream) att); } for (EventAttachment att : changeSet.deleted) { attchDao.delete(con, att.getAttachmentId()); } } return ret; } private int doEventDelete(Connection con, int eventId, boolean logicDelete) throws DAOException { EventDAO evtDao = EventDAO.getInstance(); if (logicDelete) { return evtDao.logicDeleteById(con, eventId, BaseDAO.createRevisionTimestamp()); } else { RecurrenceDAO recDao = RecurrenceDAO.getInstance(); RecurrenceBrokenDAO recbkDao = RecurrenceBrokenDAO.getInstance(); //EventAttendeeDAO attDao = EventAttendeeDAO.getInstance(); recDao.deleteByEvent(con, eventId); recbkDao.deleteByEvent(con, eventId); //attDao.deleteByEvent(con, eventId); //doEventICalendarDelete(con, eventId); return evtDao.deleteById(con, eventId); } } private int doEventsDeleteByCalendar(Connection con, int calendarId, boolean logicDelete) throws DAOException { EventDAO evtDao = EventDAO.getInstance(); if (logicDelete) { return evtDao.logicDeleteByCalendar(con, calendarId, BaseDAO.createRevisionTimestamp()); } else { RecurrenceDAO recDao = RecurrenceDAO.getInstance(); RecurrenceBrokenDAO recbkDao = RecurrenceBrokenDAO.getInstance(); //EventAttendeeDAO attDao = EventAttendeeDAO.getInstance(); //attDao.deleteByCalendar(con, calendarId); recbkDao.deleteByCalendar(con, calendarId); recDao.deleteByCalendar(con, calendarId); return evtDao.deleteByCalendar(con, calendarId); } } private void doEventMove(Connection con, boolean copy, Event event, int targetCalendarId) throws DAOException, IOException { if (copy) { EventICalendarDAO icaDao = EventICalendarDAO.getInstance(); event.setCalendarId(targetCalendarId); event.setPublicUid(null); // Reset value in order to make inner function generate new one! event.setHref(null); // Reset value in order to make inner function generate new one! OEventICalendar oica = icaDao.selectById(con, event.getEventId()); String rawICalendar = (oica != null) ? oica.getRawData() : null; //TODO: maybe add support to attachments copy doEventInsert(con, event, rawICalendar, true, false, true, false); } else { EventDAO evtDao = EventDAO.getInstance(); evtDao.updateCalendar(con, event.getEventId(), targetCalendarId, BaseDAO.createRevisionTimestamp()); } } private ORecurrenceBroken doRecurrenceExcludeDate(Connection con, OEvent recurringEvent, LocalDate instanceDate) throws DAOException { return doRecurrenceExcludeDate(con, recurringEvent.getEventId(), recurringEvent.getRecurrenceId(), instanceDate, null); } private ORecurrenceBroken doRecurrenceExcludeDate(Connection con, OEvent recurringEvent, LocalDate instanceDate, Integer brokenEventId) throws DAOException { return doRecurrenceExcludeDate(con, recurringEvent.getEventId(), recurringEvent.getRecurrenceId(), instanceDate, brokenEventId); } private ORecurrenceBroken doRecurrenceExcludeDate(Connection con, int recurringEventId, int recurrenceId, LocalDate instanceDate, Integer brokenEventId) throws DAOException { RecurrenceBrokenDAO rbkDao = RecurrenceBrokenDAO.getInstance(); // 1 - inserts a broken record on excluded date ORecurrenceBroken orb = new ORecurrenceBroken(); orb.setEventId(recurringEventId); orb.setRecurrenceId(recurrenceId); orb.setEventDate(instanceDate); orb.setNewEventId(brokenEventId); rbkDao.insert(con, orb); return orb; } private List<VExpEventInstance> doEventGetExpiredForUpdate(Connection con, DateTime fromDate, DateTime toDate) throws WTException { EventDAO evtDao = EventDAO.getInstance(); final ArrayList<VExpEventInstance> instances = new ArrayList<>(); for (VExpEvent vee : evtDao.viewExpiredForUpdateByFromTo(con, fromDate, toDate)) { VExpEventInstance item = new VExpEventInstance(); Cloner.standard().copyPropertiesOfInheritedClass(vee, item); item.setKey(EventKey.buildKey(vee.getEventId(), vee.getSeriesEventId())); instances.add(item); } for (VExpEvent vee : evtDao.viewRecurringExpiredForUpdateByFromTo(con, fromDate, toDate)) { // Returns 15 instances only, this should be enough for serving max day range from underlying reminder instances.addAll(calculateRecurringInstances(con, new VExpEventInstanceMapper(vee), fromDate, toDate, DateTimeZone.UTC, 14 + 1)); } return instances; } private boolean doEventICalendarInsert(Connection con, int eventId, String rawICalendar) throws DAOException { EventICalendarDAO icaDao = EventICalendarDAO.getInstance(); OEventICalendar ovca = new OEventICalendar(); ovca.setEventId(eventId); ovca.setRawData(rawICalendar); return icaDao.insert(con, ovca) == 1; } private boolean doEventICalendarDelete(Connection con, int eventId) throws DAOException { EventICalendarDAO icaDao = EventICalendarDAO.getInstance(); return icaDao.deleteById(con, eventId) == 1; } private OEventAttachment doEventAttachmentInsert(Connection con, int eventId, EventAttachmentWithStream attachment) throws DAOException, IOException { EventAttachmentDAO attchDao = EventAttachmentDAO.getInstance(); OEventAttachment oattch = ManagerUtils.createOTaskAttachment(attachment); oattch.setEventAttachmentId(IdentifierUtils.getUUIDTimeBased()); oattch.setEventId(eventId); attchDao.insert(con, oattch, BaseDAO.createRevisionTimestamp()); InputStream is = attachment.getStream(); try { attchDao.insertBytes(con, oattch.getEventAttachmentId(), IOUtils.toByteArray(is)); } finally { IOUtils.closeQuietly(is); } return oattch; } private boolean doEventAttachmentUpdate(Connection con, EventAttachmentWithStream attachment) throws DAOException, IOException { EventAttachmentDAO attchDao = EventAttachmentDAO.getInstance(); OEventAttachment oattch = ManagerUtils.createOTaskAttachment(attachment); attchDao.update(con, oattch, BaseDAO.createRevisionTimestamp()); InputStream is = attachment.getStream(); try { attchDao.deleteBytes(con, oattch.getEventAttachmentId()); return attchDao.insertBytes(con, oattch.getEventAttachmentId(), IOUtils.toByteArray(is)) == 1; } finally { IOUtils.closeQuietly(is); } } private OCalendar fillOCalendarWithDefaults(OCalendar tgt) { if (tgt != null) { CalendarServiceSettings ss = getServiceSettings(); if (tgt.getDomainId() == null) tgt.setDomainId(getTargetProfileId().getDomainId()); if (tgt.getUserId() == null) tgt.setUserId(getTargetProfileId().getUserId()); if (tgt.getBuiltIn() == null) tgt.setBuiltIn(false); if (StringUtils.isBlank(tgt.getProvider())) tgt.setProvider(EnumUtils.toSerializedName(Calendar.Provider.LOCAL)); if (StringUtils.isBlank(tgt.getColor())) tgt.setColor("#FFFFFF"); if (StringUtils.isBlank(tgt.getSync())) tgt.setSync(EnumUtils.toSerializedName(ss.getDefaultCalendarSync())); if (tgt.getIsDefault() == null) tgt.setIsDefault(false); if (tgt.getIsPrivate() == null) tgt.setIsPrivate(false); if (tgt.getBusy() == null) tgt.setBusy(false); if (tgt.getInvitation() == null) tgt.setInvitation(false); //if (tgt.getNotifyOnSelfUpdate() == null) tgt.setNotifyOnSelfUpdate(false); // Not yet supported! if (tgt.getNotifyOnExtUpdate() == null) tgt.setNotifyOnExtUpdate(false); Calendar.Provider provider = EnumUtils.forSerializedName(tgt.getProvider(), Calendar.Provider.class); if (Calendar.Provider.WEBCAL.equals(provider) || Calendar.Provider.CALDAV.equals(provider)) { tgt.setIsDefault(false); } } return tgt; } private OEvent fillOEventWithDefaults(OEvent tgt) { if (tgt != null) { if (StringUtils.isBlank(tgt.getPublicUid())) { tgt.setPublicUid(ManagerUtils.buildEventUid(tgt.getEventId(), WT.getDomainInternetName(getTargetProfileId().getDomainId()))); } if (StringUtils.isBlank(tgt.getHref())) tgt.setHref(ManagerUtils.buildHref(tgt.getPublicUid())); if (tgt.getReadOnly() == null) tgt.setReadOnly(false); if (StringUtils.isBlank(tgt.getOrganizer())) tgt.setOrganizer(ManagerUtils.buildOrganizer(getTargetProfileId())); } return tgt; } private List<RecipientTuple> getModificationRecipients(int calendarId, String crud) throws WTException { ArrayList<RecipientTuple> rcpts = new ArrayList<>(); // Parameter crud is not used for now! OCalendarOwnerInfo ocoi = doCalendarGetOwnerInfo(calendarId); if (ocoi != null) { UserProfileId owner = ocoi.getProfileId(); if (ocoi.getNotifyOnExtUpdate() && !owner.equals(RunContext.getRunProfileId())) { UserProfile.Data ud = WT.getUserData(owner); if (ud != null) { rcpts.add(new RecipientTuple(ud.getPersonalEmail(), owner)); } } } return rcpts; } private void notifyForEventModification(UserProfileId fromProfileId, List<RecipientTuple> recipients, EventFootprint event, String crud) { UserProfile.Data udFrom = WT.getUserData(fromProfileId); InternetAddress from = udFrom.getPersonalEmail(); Session session = getMailSession(); for (RecipientTuple rcpt : recipients) { if (!InternetAddressUtils.isAddressValid(rcpt.recipient)) { logger.warn("Recipient for event modification is invalid [{}]", rcpt.recipient); continue; } UserProfile.Data ud = WT.getUserData(rcpt.refProfileId); if (ud == null) continue; try { String title = TplHelper.buildEventModificationTitle(ud.getLocale(), event, crud); String customBodyHtml = TplHelper.buildEventModificationBody(ud.getLocale(), ud.getShortDateFormat(), ud.getShortTimeFormat(), event); String source = EmailNotification.buildSource(ud.getLocale(), SERVICE_ID); String because = lookupResource(ud.getLocale(), CalendarLocale.EMAIL_EVENTMODIFICATION_FOOTER_BECAUSE); String subject = EmailNotification.buildSubject(ud.getLocale(), SERVICE_ID, title); String html = new EmailNotification.BecauseBuilder().withCustomBody(title, customBodyHtml) .build(ud.getLocale(), source, because, rcpt.recipient.getAddress()).write(); WT.sendEmail(session, true, from, rcpt.recipient, subject, html); } catch (IOException | TemplateException | MessagingException ex) { logger.error("Unable to notify recipient after event modification [{}]", ex, rcpt.recipient); } } } private List<RecipientTuple> getInvitationRecipients(UserProfileId ownerProfile, Event event, String crud) { ArrayList<RecipientTuple> rcpts = new ArrayList<>(); // Parameter crud is not used for now! if (!event.getAttendees().isEmpty()) { String organizerAddress = event.getOrganizerAddress(); for (EventAttendee attendee : event.getAttendees()) { if (!attendee.getNotify()) continue; InternetAddress attendeeIa = attendee.getRecipientInternetAddress(); if (attendeeIa == null) continue; if (StringUtils.equalsIgnoreCase(organizerAddress, attendeeIa.getAddress()) && !EventAttendee.ResponseStatus.NEEDS_ACTION.equals(attendee.getResponseStatus())) continue; UserProfileId attProfileId = WT.guessUserProfileIdByEmailAddress(attendeeIa.getAddress()); rcpts.add(new RecipientTuple(attendeeIa, (attProfileId != null) ? attProfileId : ownerProfile)); } } return rcpts; } private void notifyForInvitation(UserProfileId senderProfileId, List<RecipientTuple> recipients, Event event, String crud) { ICalendarOutput out = new ICalendarOutput(ICalendarUtils.buildProdId(ManagerUtils.getProductName())); net.fortuna.ical4j.model.property.Method icalMethod = crud.equals(Crud.DELETE) ? net.fortuna.ical4j.model.property.Method.CANCEL : net.fortuna.ical4j.model.property.Method.REQUEST; try { InternetAddress from = WT.getUserData(senderProfileId).getPersonalEmail(); String servicePublicUrl = WT.getServicePublicUrl(senderProfileId.getDomainId(), SERVICE_ID); // Creates ical content net.fortuna.ical4j.model.Calendar ical = out.toCalendar(icalMethod, event); //net.fortuna.ical4j.model.Calendar ical = ICalHelper.toCalendar(icalMethod, prodId, event); // Creates base message parts String icalText = ICalendarUtils.calendarToString(ical); MimeBodyPart calPart = ICalendarUtils.createInvitationCalendarPart(icalMethod, icalText); String filename = ICalendarUtils.buildICalendarAttachmentFilename(WT.getPlatformName()); MimeBodyPart attPart = ICalendarUtils.createInvitationAttachmentPart(icalText, filename); IMailManager mailMgr = (IMailManager) WT.getServiceManager("com.sonicle.webtop.mail"); Session session = getMailSession(); for (RecipientTuple rcpt : recipients) { if (!InternetAddressUtils.isAddressValid(rcpt.recipient)) { logger.warn("Recipient for event invitation is invalid [{}]", rcpt.recipient); continue; } UserProfile.Data ud = WT.getUserData(rcpt.refProfileId); if (ud == null) continue; try { String title = TplHelper.buildEventInvitationTitle(ud.getLocale(), ud.getShortDateFormat(), ud.getShortTimeFormat(), event.getFootprint(), crud); String customBodyHtml = TplHelper.buildTplEventInvitationBody(ud.getLocale(), ud.getShortDateFormat(), ud.getShortTimeFormat(), event, crud, rcpt.recipient.getAddress(), servicePublicUrl); String source = NotificationHelper.buildSource(ud.getLocale(), SERVICE_ID); String because = lookupResource(ud.getLocale(), CalendarLocale.TPL_EMAIL_INVITATION_FOOTER_BECAUSE); String subject = EmailNotification.buildSubject(ud.getLocale(), SERVICE_ID, title); String html = TplHelper.buildEventInvitationHtml(ud.getLocale(), event.getTitle(), customBodyHtml, source, because, rcpt.recipient.getAddress(), crud); MimeMultipart mmp = ICalendarUtils.createInvitationPart(html, calPart, attPart); sendMail(session, mailMgr, from, rcpt.recipient, subject, mmp); } catch (IOException | TemplateException | MessagingException ex1) { logger.warn("Unable to send invitation [{}]", ex1, rcpt.recipient.getAddress()); } } } catch (IOException | MessagingException | WTException ex) { logger.warn("Unable to prepare invite notification", ex); } } private void notifyOrganizer(UserProfileId senderProfileId, Event event, String updatedAttendeeId) { CoreUserSettings cus = new CoreUserSettings(senderProfileId); String dateFormat = cus.getShortDateFormat(); String timeFormat = cus.getShortTimeFormat(); Locale locale = getProfileOrTargetLocale(senderProfileId); try { // Find the attendee (in event) that has updated its response EventAttendee targetAttendee = null; for (EventAttendee attendee : event.getAttendees()) { if (attendee.getAttendeeId().equals(updatedAttendeeId)) { targetAttendee = attendee; break; } } if (targetAttendee == null) throw new WTException("Attendee not found [{0}]", updatedAttendeeId); InternetAddress from = WT.getNotificationAddress(senderProfileId.getDomainId()); InternetAddress to = InternetAddressUtils.toInternetAddress(event.getOrganizer()); if (!InternetAddressUtils.isAddressValid(to)) throw new WTException("Organizer address not valid [{0}]", event.getOrganizer()); String servicePublicUrl = WT.getServicePublicUrl(senderProfileId.getDomainId(), SERVICE_ID); String source = NotificationHelper.buildSource(locale, SERVICE_ID); String subject = TplHelper.buildResponseUpdateTitle(locale, event, targetAttendee); String customBodyHtml = TplHelper.buildTplResponseUpdateBody(locale, dateFormat, timeFormat, event, servicePublicUrl); EmailNotification.NoReplyBuilder builder = new EmailNotification.NoReplyBuilder() .withCustomBody(event.getTitle(), customBodyHtml); if (EventAttendee.ResponseStatus.ACCEPTED.equals(targetAttendee.getResponseStatus())) { builder.greenMessage(MessageFormat.format( lookupResource(locale, CalendarLocale.TPL_EMAIL_RESPONSEUPDATE_MSG_ACCEPTED), targetAttendee.getRecipient())); } else if (EventAttendee.ResponseStatus.TENTATIVE.equals(targetAttendee.getResponseStatus())) { builder.yellowMessage(MessageFormat.format( lookupResource(locale, CalendarLocale.TPL_EMAIL_RESPONSEUPDATE_MSG_TENTATIVE), targetAttendee.getRecipient())); } else if (EventAttendee.ResponseStatus.DECLINED.equals(targetAttendee.getResponseStatus())) { builder.redMessage(MessageFormat.format( lookupResource(locale, CalendarLocale.TPL_EMAIL_RESPONSEUPDATE_MSG_DECLINED), targetAttendee.getRecipient())); } else { builder.greyMessage(MessageFormat.format( lookupResource(locale, CalendarLocale.TPL_EMAIL_RESPONSEUPDATE_MSG_OTHER), targetAttendee.getRecipient())); } String html = builder.build(locale, source).write(); WT.sendEmail(getMailSession(), true, from, to, subject, html); } catch (Exception ex) { logger.warn("Unable to notify organizer", ex); } } /* private void notifyAttendees(String crud, Event event) { notifyAttendees(getTargetProfileId(), crud, event); } private void notifyAttendees(UserProfileId senderProfileId, String crud, Event event) { if (event.getAttendees().isEmpty()) return; try { String organizerAddress = event.getOrganizerAddress(); // Finds attendees to be notified... ArrayList<EventAttendee> toBeNotified = new ArrayList<>(); for (EventAttendee attendee : event.getAttendees()) { if (!attendee.getNotify()) continue; if (StringUtils.equalsIgnoreCase(organizerAddress, attendee.getRecipientAddress()) && !EventAttendee.RESPONSE_STATUS_NEEDSACTION.equals(attendee.getResponseStatus())) continue; toBeNotified.add(attendee); //if (attendee.getNotify()) toBeNotified.add(attendee); } if (!toBeNotified.isEmpty()) { UserProfile.Data ud = WT.getUserData(senderProfileId); CoreUserSettings cus = new CoreUserSettings(senderProfileId); String dateFormat = cus.getShortDateFormat(); String timeFormat = cus.getShortTimeFormat(); String prodId = ICalendarUtils.buildProdId(ManagerUtils.getProductName()); net.fortuna.ical4j.model.property.Method icalMethod = crud.equals(Crud.DELETE) ? net.fortuna.ical4j.model.property.Method.CANCEL : net.fortuna.ical4j.model.property.Method.REQUEST; // Creates ical content net.fortuna.ical4j.model.Calendar ical = ICalHelper.toCalendar(icalMethod, prodId, event); // Creates base message parts String icalText = ICalendarUtils.calendarToString(ical); MimeBodyPart calPart = ICalendarUtils.createInvitationCalendarPart(icalMethod, icalText); String filename = ICalendarUtils.buildICalendarAttachmentFilename(WT.getPlatformName()); MimeBodyPart attPart = ICalendarUtils.createInvitationAttachmentPart(icalText, filename); String source = NotificationHelper.buildSource(ud.getLocale(), SERVICE_ID); String subject = TplHelper.buildEventInvitationEmailSubject(ud.getLocale(), dateFormat, timeFormat, event, crud); String because = lookupResource(ud.getLocale(), CalendarLocale.TPL_EMAIL_INVITATION_FOOTER_BECAUSE); String servicePublicUrl = WT.getServicePublicUrl(senderProfileId.getDomainId(), SERVICE_ID); IMailManager mailMgr = (IMailManager)WT.getServiceManager("com.sonicle.webtop.mail"); Session session = getMailSession(); InternetAddress from = ud.getEmail(); for (EventAttendee attendee : toBeNotified) { InternetAddress to = InternetAddressUtils.toInternetAddress(attendee.getRecipient()); if (InternetAddressUtils.isAddressValid(to)) { final String customBody = TplHelper.buildEventInvitationBodyTpl(ud.getLocale(), dateFormat, timeFormat, event, crud, attendee.getAddress(), servicePublicUrl); final String html = TplHelper.buildInvitationTpl(ud.getLocale(), source, attendee.getAddress(), event.getTitle(), customBody, because, crud); try { MimeMultipart mmp = ICalendarUtils.createInvitationPart(html, calPart, attPart); if (mailMgr != null) { try { mailMgr.sendMessage(from, Arrays.asList(to), null, null, subject, mmp); } catch(WTException ex1) { logger.warn("Unable to send using mail service", ex1); WT.sendEmail(session, from, Arrays.asList(to), null, null, subject, mmp); } } else { WT.sendEmail(session, from, Arrays.asList(to), null, null, subject, mmp); } } catch(MessagingException ex) { logger.warn("Unable to send notification to attendee {}", to.toString()); } } } } } catch(Exception ex) { logger.warn("Unable notify attendees", ex); } } */ private void sendMail(Session session, IMailManager mailMgr, InternetAddress from, InternetAddress to, String subject, MimeMultipart mpart) throws MessagingException { boolean fallback = true; if (mailMgr != null) { try { mailMgr.sendMessage(from, Arrays.asList(to), null, null, subject, mpart); fallback = false; } catch (WTException ex) { logger.warn("Unable to send using mail service, falling back to standard send...", ex); } } if (fallback) WT.sendEmail(session, from, Arrays.asList(to), null, null, subject, mpart); } private VVEventInstance cloneEvent(VVEventInstance sourceEvent, DateTime newStart, DateTime newEnd) { VVEventInstance event = new VVEventInstance(sourceEvent); event.setStartDate(newStart); event.setEndDate(newEnd); return event; } private void checkRightsOnCalendarRoot(UserProfileId owner, String action) throws WTException { UserProfileId targetPid = getTargetProfileId(); if (RunContext.isWebTopAdmin()) return; if (owner.equals(targetPid)) return; String shareId = shareCache.getShareRootIdByOwner(owner); if (shareId == null) throw new WTException("ownerToRootShareId({0}) -> null", owner); CoreManager core = WT.getCoreManager(targetPid); if (core.isShareRootPermitted(shareId, action)) return; throw new AuthException("Action not allowed on root share [{0}, {1}, {2}, {3}]", shareId, action, GROUPNAME_CALENDAR, targetPid.toString()); } private boolean quietlyCheckRightsOnCalendarFolder(int calendarId, String action) { try { checkRightsOnCalendarFolder(calendarId, action); return true; } catch (AuthException ex1) { return false; } catch (WTException ex1) { logger.warn("Unable to check rights [{}]", calendarId); return false; } } private void checkRightsOnCalendarFolder(int calendarId, String action) throws WTException { UserProfileId targetPid = getTargetProfileId(); if (RunContext.isWebTopAdmin()) return; // Skip rights check if running user is resource's owner UserProfileId owner = ownerCache.get(calendarId); if (owner == null) throw new WTException("calendarToOwner({0}) -> null", calendarId); if (owner.equals(targetPid)) return; // Checks rights on the wildcard instance (if present) CoreManager core = WT.getCoreManager(targetPid); String wildcardShareId = shareCache.getWildcardShareFolderIdByOwner(owner); if (wildcardShareId != null) { if (core.isShareFolderPermitted(wildcardShareId, action)) return; } // Checks rights on calendar instance String shareId = shareCache.getShareFolderIdByFolderId(calendarId); if (shareId == null) throw new WTException("calendarToLeafShareId({0}) -> null", calendarId); if (core.isShareFolderPermitted(shareId, action)) return; throw new AuthException("Action not allowed on folder share [{0}, {1}, {2}, {3}]", shareId, action, GROUPNAME_CALENDAR, targetPid.toString()); } private void checkRightsOnCalendarElements(int calendarId, String action) throws WTException { UserProfileId targetPid = getTargetProfileId(); if (RunContext.isWebTopAdmin()) return; // Skip rights check if running user is resource's owner UserProfileId owner = ownerCache.get(calendarId); if (owner == null) throw new WTException("calendarToOwner({0}) -> null", calendarId); if (owner.equals(targetPid)) return; // Checks rights on the wildcard instance (if present) CoreManager core = WT.getCoreManager(targetPid); String wildcardShareId = shareCache.getWildcardShareFolderIdByOwner(owner); if (wildcardShareId != null) { if (core.isShareElementsPermitted(wildcardShareId, action)) return; //if(core.isShareElementsPermitted(SERVICE_ID, RESOURCE_CALENDAR, action, wildcardShareId)) return; } // Checks rights on calendar instance String shareId = shareCache.getShareFolderIdByFolderId(calendarId); if (shareId == null) throw new WTException("calendarToLeafShareId({0}) -> null", calendarId); if (core.isShareElementsPermitted(shareId, action)) return; //if (core.isShareElementsPermitted(SERVICE_ID, RESOURCE_CALENDAR, action, shareId)) return; throw new AuthException("Action not allowed on elements share [{0}, {1}, {2}, {3}]", shareId, action, GROUPNAME_CALENDAR, targetPid.toString()); } private ReminderInApp createEventReminderAlertWeb(VExpEventInstance instance) { ReminderInApp alert = new ReminderInApp(SERVICE_ID, instance.getCalendarProfileId(), "event", instance.getKey()); alert.setTitle(instance.getTitle()); alert.setDate(instance.getStartDate().withZone(instance.getDateTimeZone())); alert.setTimezone(instance.getTimezone()); return alert; } private ReminderEmail createEventReminderAlertEmail(Locale locale, String dateFormat, String timeFormat, String recipientEmail, UserProfileId ownerId, EventInstance event) throws WTException { ReminderEmail alert = new ReminderEmail(SERVICE_ID, ownerId, "event", event.getKey()); try { String source = NotificationHelper.buildSource(locale, SERVICE_ID); String because = lookupResource(locale, CalendarLocale.EMAIL_REMINDER_FOOTER_BECAUSE); String customBodyHtml = TplHelper.buildTplEventReminderBody(locale, dateFormat, timeFormat, event); String title = TplHelper.buildEventReminderTitle(locale, dateFormat, timeFormat, event.getFootprint()); String html = TplHelper.buildEventInvitationHtml(locale, event.getTitle(), customBodyHtml, source, because, recipientEmail, null); //String body = TplHelper.buildInvitationTpl(locale, source, recipientEmail, event.getTitle(), customBodyHtml, because, null); alert.setSubject(EmailNotification.buildSubject(locale, SERVICE_ID, title)); alert.setBody(html); } catch (IOException | TemplateException | AddressException ex) { throw new WTException(ex); } return alert; } private void storeAsSuggestion(CoreManager coreMgr, String context, String value) { if (StringUtils.isBlank(value)) return; coreMgr.addServiceStoreEntry(SERVICE_ID, context, value.toUpperCase(), value); } private enum DoOption { SKIP, UPDATE } private class SchedEventInstanceMapper implements RecurringInstanceMapper<SchedEventInstance> { private final VVEvent event; public SchedEventInstanceMapper(VVEvent event) { this.event = event; } @Override public int getEventId() { return event.getEventId(); } @Override public DateTime getEventStartDate() { return event.getStartDate(); } @Override public DateTime getEventEndDate() { return event.getEndDate(); } @Override public DateTimeZone getEventTimezone() { return event.getDateTimezone(); } @Override public SchedEventInstance createInstance(String key, DateTime startDate, DateTime endDate) { SchedEventInstance item = ManagerUtils.fillSchedEvent(new SchedEventInstance(), event); item.setKey(key); item.setStartDate(startDate); item.setEndDate(endDate); return item; } } private class VExpEventInstanceMapper implements RecurringInstanceMapper<VExpEventInstance> { private final VExpEvent event; public VExpEventInstanceMapper(VExpEvent event) { this.event = event; } @Override public int getEventId() { return event.getEventId(); } @Override public DateTime getEventStartDate() { return event.getStartDate(); } @Override public DateTime getEventEndDate() { return event.getEndDate(); } @Override public DateTimeZone getEventTimezone() { return event.getDateTimeZone(); } @Override public VExpEventInstance createInstance(String key, DateTime startDate, DateTime endDate) { VExpEventInstance item = new VExpEventInstance(); Cloner.standard().copyPropertiesOfInheritedClass(event, item); item.setKey(key); item.setStartDate(startDate); item.setEndDate(endDate); /* VExpEventInstance item = new VExpEventInstance(); item.setKey(key); item.setEventId(event.getEventId()); item.setCalendarId(event.getCalendarId()); item.setRecurrenceId(event.getRecurrenceId()); item.setStartDate(startDate); item.setEndDate(endDate); item.setTimezone(event.getTimezone()); item.setAllDay(event.getAllDay()); item.setTitle(event.getTitle()); item.setReminder(event.getReminder()); item.setRemindedOn(event.getRemindedOn()); item.setCalendarDomainId(event.getCalendarDomainId()); item.setCalendarUserId(event.getCalendarUserId()); item.setSeriesEventId(event.getSeriesEventId()); item.setHasAttendees(event.getHasAttendees()); //item.setRecurInfo(src.isEventRecurring(), src.isEventBroken()); */ return item; } } private class EventMapper implements RecurringInstanceMapper<Event> { private final Event event; final Cloner cloner; public EventMapper(Event event) { this.event = event; this.cloner = Cloner.standard(); } @Override public int getEventId() { return event.getEventId(); } @Override public DateTime getEventStartDate() { return event.getStartDate(); } @Override public DateTime getEventEndDate() { return event.getEndDate(); } @Override public DateTimeZone getEventTimezone() { return event.getDateTimeZone(); } @Override public Event createInstance(String key, DateTime startDate, DateTime endDate) { Event clone = cloner.deepClone(event); clone.setStartDate(startDate); clone.setEndDate(endDate); return clone; } } private class VVEventInstanceMapper implements RecurringInstanceMapper<VVEventInstance> { private final VVEvent event; final Cloner cloner; public VVEventInstanceMapper(VVEvent event) { this.event = event; this.cloner = Cloner.standard(); } @Override public int getEventId() { return event.getEventId(); } @Override public DateTime getEventStartDate() { return event.getStartDate(); } @Override public DateTime getEventEndDate() { return event.getEndDate(); } @Override public DateTimeZone getEventTimezone() { return event.getDateTimezone(); } @Override public VVEventInstance createInstance(String key, DateTime startDate, DateTime endDate) { VVEvent clone = cloner.deepClone(event); clone.setStartDate(startDate); clone.setEndDate(endDate); return new VVEventInstance(key, clone); } } private interface RecurringInstanceMapper<T> { public int getEventId(); public DateTime getEventStartDate(); public DateTime getEventEndDate(); public DateTimeZone getEventTimezone(); public T createInstance(String key, DateTime startDate, DateTime endDate); } public static class EventInsertResult { public final OEvent event; public final ORecurrence recurrence; public final List<ORecurrenceBroken> recurrenceBrokens; public final List<OEventAttendee> attendees; public final List<OEventAttachment> attachments; public EventInsertResult(OEvent event, ORecurrence recurrence, List<ORecurrenceBroken> recurrenceBrokens, ArrayList<OEventAttendee> attendees, List<OEventAttachment> attachments) { this.event = event; this.recurrence = recurrence; this.recurrenceBrokens = recurrenceBrokens; this.attendees = attendees; this.attachments = attachments; } } public static class ProbeCalendarRemoteUrlResult { public final String displayName; public ProbeCalendarRemoteUrlResult(String displayName) { this.displayName = displayName; } } private static class RecipientTuple { public final InternetAddress recipient; public final UserProfileId refProfileId; public RecipientTuple(InternetAddress recipient, UserProfileId profileId) { this.recipient = recipient; this.refProfileId = profileId; } } private class OwnerCache extends AbstractMapCache<Integer, UserProfileId> { @Override protected void internalInitCache() { } @Override protected void internalMissKey(Integer key) { try { UserProfileId owner = doCalendarGetOwner(key); if (owner == null) throw new WTException("Owner not found [{0}]", key); put(key, owner); } catch (WTException ex) { throw new WTRuntimeException(ex.getMessage()); } } } private class ShareCache extends AbstractShareCache<Integer, ShareRootCalendar> { @Override protected void internalInitCache() { final CoreManager coreMgr = WT.getCoreManager(getTargetProfileId()); try { for (ShareRootCalendar root : internalListIncomingCalendarShareRoots()) { shareRoots.add(root); ownerToShareRoot.put(root.getOwnerProfileId(), root); for (OShare folder : coreMgr.listIncomingShareFolders(root.getShareId(), GROUPNAME_CALENDAR)) { if (folder.hasWildcard()) { final UserProfileId ownerPid = coreMgr.userUidToProfileId(folder.getUserUid()); ownerToWildcardShareFolder.put(ownerPid, folder.getShareId().toString()); for (Calendar calendar : listCalendars(ownerPid).values()) { folderTo.add(calendar.getCalendarId()); rootShareToFolderShare.put(root.getShareId(), calendar.getCalendarId()); folderToWildcardShareFolder.put(calendar.getCalendarId(), folder.getShareId().toString()); } } else { int categoryId = Integer.valueOf(folder.getInstance()); folderTo.add(categoryId); rootShareToFolderShare.put(root.getShareId(), categoryId); folderToShareFolder.put(categoryId, folder.getShareId().toString()); } } } ready = true; } catch (WTException ex) { throw new WTRuntimeException(ex.getMessage()); } } } }