///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as published // by the Free Software Foundation; version 3 of the License. // // This community edition 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 General // Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.plugins.teamcal.event; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Set; import java.util.TimeZone; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.log4j.Logger; import org.hibernate.Query; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.projectforge.calendar.CalendarUtils; import org.projectforge.calendar.ICal4JUtils; import org.projectforge.common.DateHelper; import org.projectforge.common.DateHolder; import org.projectforge.core.BaseDao; import org.projectforge.core.BaseSearchFilter; import org.projectforge.core.DisplayHistoryEntry; import org.projectforge.core.QueryFilter; import org.projectforge.plugins.teamcal.TeamCalConfig; import org.projectforge.plugins.teamcal.admin.TeamCalCache; import org.projectforge.plugins.teamcal.admin.TeamCalDO; import org.projectforge.plugins.teamcal.admin.TeamCalDao; import org.projectforge.plugins.teamcal.admin.TeamCalsProvider; import org.projectforge.plugins.teamcal.externalsubscription.TeamEventExternalSubscriptionCache; import org.projectforge.user.PFUserContext; import org.projectforge.user.UserRightId; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * * @author Kai Reinhard (k.reinhard@micromata.de) * @author M. Lauterbach (m.lauterbach@micromata.de) * */ @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public class TeamEventDao extends BaseDao<TeamEventDO> { public static final UserRightId USER_RIGHT_ID = new UserRightId("PLUGIN_CALENDAR_EVENT", "plugin15", "plugins.teamcalendar.event"); public static final long MIN_DATE_1800 = -5364662400000L; public static final long MAX_DATE_3000 = 32535216000000L; private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TeamEventDao.class); private static final long ONE_DAY = 1000 * 60 * 60 * 24; private static final Class< ? >[] ADDITIONAL_HISTORY_SEARCH_DOS = new Class[] { TeamEventAttendeeDO.class}; private static final String[] ADDITIONAL_SEARCH_FIELDS = new String[] { "subject", "location", "calendar.id", "calendar.title", "note", "attendees"}; private TeamCalDao teamCalDao; public TeamEventDao() { super(TeamEventDO.class); userRightId = USER_RIGHT_ID; } @Override protected String[] getAdditionalSearchFields() { return ADDITIONAL_SEARCH_FIELDS; } /** * @param teamEvent * @param teamCalendarId If null, then team calendar will be set to null; * @see BaseDao#getOrLoad(Integer) */ public void setCalendar(final TeamEventDO teamEvent, final Integer teamCalendarId) { final TeamCalDO teamCal = teamCalDao.getOrLoad(teamCalendarId); teamEvent.setCalendar(teamCal); } public TeamEventDO getByUid(final String uid) { return getByUid(uid, null); } @SuppressWarnings("unchecked") public TeamEventDO getByUid(final String uid, final Integer teamCalId) { if (uid == null) { return null; } List<TeamEventDO> list; final Integer id = TeamCalConfig.get().extractEventId(uid); if (teamCalId != null) { if (id != null) { // The uid refers an own event, therefore search for the extracted id. list = getHibernateTemplate().find("from TeamEventDO e where e.id=? and e.calendar.id=? and e.deleted=false", id, teamCalId); } else { // It's an external event: list = getHibernateTemplate().find("from TeamEventDO e where e.externalUid=? and e.calendar.id=? and e.deleted=false", uid, teamCalId); } } else { if (id != null) { // The uid refers an own event, therefore search for the extracted id. list = getHibernateTemplate().find("from TeamEventDO e where e.id=? and e.deleted=false", id); } else { // It's an external event: list = getHibernateTemplate().find("from TeamEventDO e where e.externalUid=? and e.deleted=false", uid); } } if (list != null && list.isEmpty() == false && list.get(0) != null) { return list.get(0); } return null; } /** * Sets midnight (UTC) of all day events. * @see org.projectforge.core.BaseDao#onSaveOrModify(org.projectforge.core.ExtendedBaseDO) */ @Override protected void onSaveOrModify(final TeamEventDO event) { super.onSaveOrModify(event); Validate.notNull(event.getCalendar()); if (event.isAllDay() == true) { final Date startDate = event.getStartDate(); if (startDate != null) { event.setStartDate(CalendarUtils.getUTCMidnightTimestamp(startDate)); } final Date endDate = event.getEndDate(); if (endDate != null) { event.setEndDate(CalendarUtils.getUTCMidnightTimestamp(endDate)); } } // Update recurrenceUntil date (for database queries): final Date recurrenceUntil = ICal4JUtils.calculateRecurrenceUntil(event.getRecurrenceRule()); event.setRecurrenceUntil(recurrenceUntil); } /** * This method also returns recurrence events outside the time period of the given filter but affecting the time-period (e. g. older * recurrence events without end date or end date inside or after the given time period). If calculateRecurrenceEvents is true, only the * recurrence events inside the given time-period are returned, if false only the origin recurrence event (may-be outside the given * time-period) is returned. * @param filter * @param calculateRecurrenceEvents If true, recurrence events inside the given time-period are calculated. * @return list of team events (same as {@link #getList(BaseSearchFilter)} but with all calculated and matching recurrence events (if * calculateRecurrenceEvents is true). Origin events are of type {@link TeamEventDO}, calculated events of type {@link TeamEvent}. */ public List<TeamEvent> getEventList(final TeamEventFilter filter, final boolean calculateRecurrenceEvents) { final List<TeamEvent> result = new ArrayList<TeamEvent>(); List<TeamEventDO> list = getList(filter); if (CollectionUtils.isNotEmpty(list) == true) { for (final TeamEventDO eventDO : list) { if (eventDO.hasRecurrence() == true) { // Added later. continue; } result.add(eventDO); } } final TeamEventFilter teamEventFilter = filter.clone().setOnlyRecurrence(true); final QueryFilter qFilter = buildQueryFilter(teamEventFilter); qFilter.add(Restrictions.isNotNull("recurrenceRule")); list = getList(qFilter); list = selectUnique(list); // add all abo events final List<TeamEventDO> recurrenceEvents = TeamEventExternalSubscriptionCache.instance().getRecurrenceEvents(teamEventFilter); if (recurrenceEvents != null && recurrenceEvents.size() > 0) { list.addAll(recurrenceEvents); } final TimeZone timeZone = PFUserContext.getTimeZone(); if (list != null) { for (final TeamEventDO eventDO : list) { if (eventDO.hasRecurrence() == false) { log.warn("Shouldn't occur! Please contact developer."); // This event was handled above. continue; } if (calculateRecurrenceEvents == false) { result.add(eventDO); continue; } final Collection<TeamEvent> events = TeamEventUtils.getRecurrenceEvents(teamEventFilter.getStartDate(), teamEventFilter.getEndDate(), eventDO, timeZone); if (events == null) { continue; } for (final TeamEvent event : events) { if (matches(event.getStartDate(), event.getEndDate(), event.isAllDay(), teamEventFilter) == false) { continue; } result.add(event); } } } return result; } /** * @see org.projectforge.core.BaseDao#getListForSearchDao(org.projectforge.core.BaseSearchFilter) */ @Override public List<TeamEventDO> getListForSearchDao(final BaseSearchFilter filter) { final TeamEventFilter teamEventFilter = new TeamEventFilter(filter); // May-be called by SeachPage final Collection<TeamCalDO> ownCalendars = TeamCalCache.getInstance().getAllOwnCalendars(); if (CollectionUtils.isEmpty(ownCalendars) == true) { // No calendars accessible, nothing to search. return new ArrayList<TeamEventDO>(); } teamEventFilter.setTeamCals(TeamCalsProvider.getCalIdList(ownCalendars)); return getList(teamEventFilter); } /** * @see org.projectforge.core.BaseDao#getList(org.projectforge.core.BaseSearchFilter) */ @Override @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public List<TeamEventDO> getList(final BaseSearchFilter filter) { final TeamEventFilter teamEventFilter; if (filter instanceof TeamEventFilter) { teamEventFilter = ((TeamEventFilter) filter).clone(); } else { teamEventFilter = new TeamEventFilter(filter); } if (CollectionUtils.isEmpty(teamEventFilter.getTeamCals()) == true && teamEventFilter.getTeamCalId() == null) { return new ArrayList<TeamEventDO>(); } final QueryFilter qFilter = buildQueryFilter(teamEventFilter); final List<TeamEventDO> list = getList(qFilter); final List<TeamEventDO> result = new ArrayList<TeamEventDO>(list.size()); if (list != null) { for (final TeamEventDO event : list) { if (matches(event.getStartDate(), event.getEndDate(), event.isAllDay(), teamEventFilter) == true) { result.add(event); } } } // subscriptions final TeamEventExternalSubscriptionCache aboCache = TeamEventExternalSubscriptionCache.instance(); final List<Integer> alreadyAdded = new ArrayList<Integer>(); // precondition for abos: existing teamcals in filter if (teamEventFilter.getTeamCals() != null) { for (final Integer calendarId : teamEventFilter.getTeamCals()) { if (aboCache.isExternalSubscribedCalendar(calendarId) == true) { addEventsToList(teamEventFilter, result, aboCache, calendarId); alreadyAdded.add(calendarId); } } } // if the getTeamCalId is not null and we do not added this before, do it now final Integer teamCalId = teamEventFilter.getTeamCalId(); if (teamCalId != null && alreadyAdded.contains(teamCalId) == false) { if (aboCache.isExternalSubscribedCalendar(teamCalId) == true) { addEventsToList(teamEventFilter, result, aboCache, teamCalId); } } return result; } /** * Get all locations of the user's calendar events (not deleted ones) with modification date within last year. * @param searchString */ @SuppressWarnings("unchecked") public List<String> getLocationAutocompletion(final String searchString, final TeamCalDO[] calendars) { if (calendars == null || calendars.length == 0) { return null; } if (StringUtils.isBlank(searchString) == true) { return null; } checkLoggedInUserSelectAccess(); final String s = "select distinct location from " + clazz.getSimpleName() + " t where deleted=false and t.calendar in :cals and lastUpdate > :lastUpdate and lower(t.location) like :location) order by t.location"; final Query query = getSession().createQuery(s); query.setParameterList("cals", calendars); final DateHolder dh = new DateHolder(); dh.add(Calendar.YEAR, -1); query.setDate("lastUpdate", dh.getDate()); query.setString("location", "%" + StringUtils.lowerCase(searchString) + "%"); final List<String> list = query.list(); return list; } private void addEventsToList(final TeamEventFilter teamEventFilter, final List<TeamEventDO> result, final TeamEventExternalSubscriptionCache aboCache, final Integer calendarId) { final Date startDate = teamEventFilter.getStartDate(); final Date endDate = teamEventFilter.getEndDate(); final Long startTime = startDate == null ? 0 : startDate.getTime(); final Long endTime = endDate == null ? MAX_DATE_3000 : endDate.getTime(); final List<TeamEventDO> events = aboCache.getEvents(calendarId, startTime, endTime); if (events != null && events.size() > 0) { result.addAll(events); } } private boolean matches(final Date eventStartDate, final Date eventEndDate, final boolean allDay, final TeamEventFilter teamEventFilter) { final Date startDate = teamEventFilter.getStartDate(); final Date endDate = teamEventFilter.getEndDate(); if (allDay == true) { // Check date match: final Calendar utcCal = Calendar.getInstance(DateHelper.UTC); utcCal.setTime(eventStartDate); if (startDate != null && eventEndDate.before(startDate) == true) { // Check same day (eventStartDate in UTC and startDate of filter in user's time zone): final Calendar userCal = Calendar.getInstance(PFUserContext.getTimeZone()); userCal.setTime(startDate); if (CalendarUtils.isSameDay(utcCal, utcCal) == true) { return true; } return false; } if (endDate != null && eventStartDate.after(endDate) == true) { // Check same day (eventEndDate in UTC and endDate of filter in user's time zone): final Calendar userCal = Calendar.getInstance(PFUserContext.getTimeZone()); userCal.setTime(endDate); if (CalendarUtils.isSameDay(utcCal, utcCal) == true) { return true; } return false; } return true; } else { // Check start and stop date due to extension of time period of buildQueryFilter: if (startDate != null && eventEndDate.before(startDate) == true) { return false; } if (endDate != null && eventStartDate.after(endDate) == true) { return false; } } return true; } /** * The time period of the filter will be extended by one day. This is needed due to all day events which are stored in UTC. The additional * events in the result list not matching the time period have to be removed by caller! * @param filter * @return */ private QueryFilter buildQueryFilter(final TeamEventFilter filter) { final QueryFilter queryFilter = new QueryFilter(filter); final Collection<Integer> cals = filter.getTeamCals(); if (CollectionUtils.isNotEmpty(cals) == true) { queryFilter.add(Restrictions.in("calendar.id", cals)); } else if (filter.getTeamCalId() != null) { queryFilter.add(Restrictions.eq("calendar.id", filter.getTeamCalId())); } // Following period extension is needed due to all day events which are stored in UTC. The additional events in the result list not // matching the time period have to be removed by caller! Date startDate = filter.getStartDate(); if (startDate != null) { startDate = new Date(startDate.getTime() - ONE_DAY); } Date endDate = filter.getEndDate(); if (endDate != null) { endDate = new Date(endDate.getTime() + ONE_DAY); } // limit events to load to chosen date view. if (startDate != null && endDate != null) { if (filter.isOnlyRecurrence() == false) { queryFilter.add(Restrictions.or( (Restrictions.or(Restrictions.between("startDate", startDate, endDate), Restrictions.between("endDate", startDate, endDate))), // get events whose duration overlap with chosen duration. (Restrictions.and(Restrictions.le("startDate", startDate), Restrictions.ge("endDate", endDate))))); } else { queryFilter.add( // "startDate" < endDate && ("recurrenceUntil" == null || "recurrenceUntil" > startDate) (Restrictions.and(Restrictions.lt("startDate", endDate), Restrictions.or(Restrictions.isNull("recurrenceUntil"), Restrictions.gt("recurrenceUntil", startDate))))); } } else if (startDate != null) { if (filter.isOnlyRecurrence() == false) { queryFilter.add(Restrictions.ge("startDate", startDate)); } else { // This branch is reached for subscriptions and calendar downloads. queryFilter.add( // "recurrenceUntil" == null || "recurrenceUntil" > startDate Restrictions.or(Restrictions.isNull("recurrenceUntil"), Restrictions.gt("recurrenceUntil", startDate))); } } else if (endDate != null) { queryFilter.add(Restrictions.le("startDate", endDate)); } queryFilter.addOrder(Order.desc("startDate")); if (log.isDebugEnabled() == true) { log.debug(ToStringBuilder.reflectionToString(filter)); } return queryFilter; } /** * Gets history entries of super and adds all history entries of the TeamEventAttendeeDO childs. * @see org.projectforge.core.BaseDao#getDisplayHistoryEntries(org.projectforge.core.ExtendedBaseDO) */ @Override public List<DisplayHistoryEntry> getDisplayHistoryEntries(final TeamEventDO obj) { final List<DisplayHistoryEntry> list = super.getDisplayHistoryEntries(obj); if (hasLoggedInUserHistoryAccess(obj, false) == false) { return list; } if (CollectionUtils.isNotEmpty(obj.getAttendees()) == true) { for (final TeamEventAttendeeDO attendee : obj.getAttendees()) { final List<DisplayHistoryEntry> entries = internalGetDisplayHistoryEntries(attendee); for (final DisplayHistoryEntry entry : entries) { final String propertyName = entry.getPropertyName(); if (propertyName != null) { entry.setPropertyName(attendee.toString() + ":" + entry.getPropertyName()); // Prepend user name or url to identify. } else { entry.setPropertyName(attendee.toString()); } } list.addAll(entries); } } Collections.sort(list, new Comparator<DisplayHistoryEntry>() { public int compare(final DisplayHistoryEntry o1, final DisplayHistoryEntry o2) { return (o2.getTimestamp().compareTo(o1.getTimestamp())); } }); return list; } @Override protected Class< ? >[] getAdditionalHistorySearchDOs() { return ADDITIONAL_HISTORY_SEARCH_DOS; } /** * Returns also true, if idSet contains the id of any attendee. * @see org.projectforge.core.BaseDao#contains(java.util.Set, org.projectforge.core.ExtendedBaseDO) */ @Override protected boolean contains(final Set<Integer> idSet, final TeamEventDO entry) { if (super.contains(idSet, entry) == true) { return true; } for (final TeamEventAttendeeDO pos : entry.getAttendees()) { if (idSet.contains(pos.getId()) == true) { return true; } } return false; } @Override public TeamEventDO newInstance() { return new TeamEventDO(); } /** * @return the log */ public Logger getLog() { return log; } /** * @see org.projectforge.core.BaseDao#useOwnCriteriaCacheRegion() */ @Override protected boolean useOwnCriteriaCacheRegion() { return true; } /** * @param teamCalDao the teamCalDao to set */ public void setTeamCalDao(final TeamCalDao teamCalDao) { this.teamCalDao = teamCalDao; } }