/////////////////////////////////////////////////////////////////////////////
//
// 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.externalsubscription;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.hibernate.criterion.Restrictions;
import org.projectforge.common.DateHelper;
import org.projectforge.core.QueryFilter;
import org.projectforge.plugins.teamcal.admin.TeamCalAccessType;
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.TeamCalRight;
import org.projectforge.plugins.teamcal.event.TeamEventDO;
import org.projectforge.plugins.teamcal.event.TeamEventFilter;
import org.projectforge.user.PFUserContext;
import org.projectforge.user.UserRights;
/**
* @author Johannes Unterstein (j.unterstein@micromata.de)
*/
public class TeamEventExternalSubscriptionCache
{
private static final TeamEventExternalSubscriptionCache instance = new TeamEventExternalSubscriptionCache();
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TeamEventExternalSubscriptionCache.class);
private static final long MAX_WAIT_MS_AFTER_FAILED_UPDATE = 1000 * 60 * 60 * 24; // 24 h
private final Map<Integer, TeamEventSubscription> subscriptions;
private static final Long SUBSCRIPTION_UPDATE_TIME = 5L * 60 * 1000; // 5 min
private transient TeamCalRight teamCalRight;
private TeamEventExternalSubscriptionCache()
{
subscriptions = new HashMap<Integer, TeamEventSubscription>();
}
public static TeamEventExternalSubscriptionCache instance()
{
return instance;
}
public void updateCache(final TeamCalDao dao)
{
final QueryFilter filter = new QueryFilter();
filter.add(Restrictions.eq("externalSubscription", true));
// internalGetList is valid at this point, because we are calling this method in an asyn thread
final List<TeamCalDO> subscribedCalendars = dao.internalGetList(filter);
for (final TeamCalDO calendar : subscribedCalendars) {
updateCache(dao, calendar);
}
final List<Integer> idsToRemove = new ArrayList<Integer>();
for (final Integer calendarId : subscriptions.keySet()) {
// if calendar is not subscribed anymore, remove them
if (calendarListContainsId(subscribedCalendars, calendarId) == false) {
idsToRemove.add(calendarId);
}
}
removeCalendarsFromCache(idsToRemove);
}
private void removeCalendarsFromCache(final List<Integer> idsToRemove)
{
for (final Integer calendarId : idsToRemove) {
subscriptions.remove(calendarId);
}
}
private boolean calendarListContainsId(final List<TeamCalDO> subscribedCalendars, final Integer calendarId)
{
for (final TeamCalDO teamCal : subscribedCalendars) {
if (teamCal.getId().equals(calendarId)) {
return true;
}
}
return false;
}
public void updateCache(final TeamCalDao dao, final TeamCalDO calendar)
{
updateCache(dao, calendar, false);
}
/**
* @param dao
* @param calendar
* @param force If true then update is forced (independent of last update time and refresh interval).
*/
public void updateCache(final TeamCalDao dao, final TeamCalDO calendar, final boolean force)
{
final Integer calId = calendar.getId();
if (calId == null) {
log.error("Oups, calId is null (can't update subscription): " + calendar);
return;
}
TeamEventSubscription teamEventSubscription = subscriptions.get(calId);
final Long now = System.currentTimeMillis();
final Long addedTime = calendar.getExternalSubscriptionUpdateInterval() == null ? SUBSCRIPTION_UPDATE_TIME : 1000L * calendar
.getExternalSubscriptionUpdateInterval();
if (teamEventSubscription == null) {
// First update of subscribed calendar:
teamEventSubscription = new TeamEventSubscription();
subscriptions.put(calendar.getId(), teamEventSubscription);
teamEventSubscription.update(dao, calendar);
} else if (force == true || teamEventSubscription.getLastUpdated() == null || teamEventSubscription.getLastUpdated() + addedTime <= now) {
if (force == false && teamEventSubscription.getNumberOfFailedUpdates() > 0) {
// Errors occurred and update not forced. Don't update e. g. every 5 minutes if a permanently error occurs.
Long lastRun = teamEventSubscription.getLastUpdated();
if (lastRun == null) {
lastRun = teamEventSubscription.getLastFailedUpdate();
}
if (lastRun == null || lastRun + teamEventSubscription.getNumberOfFailedUpdates() * addedTime <= now) {
teamEventSubscription.update(dao, calendar);
} else if (lastRun + MAX_WAIT_MS_AFTER_FAILED_UPDATE > now) {
log.info("Try to update subscribed calendar after "
+ (MAX_WAIT_MS_AFTER_FAILED_UPDATE / 1000 / 60 / 60)
+ " hours. Number of failed updates: "
+ teamEventSubscription.getNumberOfFailedUpdates()
+ ", time of last successful update (UTC): "
+ (teamEventSubscription.getLastUpdated() != null ? DateHelper.formatAsUTC(new Date(teamEventSubscription.getLastUpdated()))
: "-"));
teamEventSubscription.update(dao, calendar);
}
} else {
// update the calendar
teamEventSubscription.update(dao, calendar);
}
}
}
public boolean isExternalSubscribedCalendar(final Integer calendarId)
{
return subscriptions.keySet().contains(calendarId) == true;
}
public List<TeamEventDO> getEvents(final Integer calendarId, final Long startTime, final Long endTime)
{
final TeamEventSubscription eventSubscription = subscriptions.get(calendarId);
if (eventSubscription == null) {
return null;
}
final Integer userId = PFUserContext.getUserId();
final TeamCalAccessType accessType = getAccessType(eventSubscription.getTeamCalId(), userId);
if (accessType == TeamCalAccessType.NONE) {
return null;
}
return eventSubscription.getEvents(startTime, endTime, accessType == TeamCalAccessType.MINIMAL);
}
public List<TeamEventDO> getRecurrenceEvents(final TeamEventFilter filter)
{
final List<TeamEventDO> result = new ArrayList<TeamEventDO>();
// precondition: existing teamcals ins filter
final Collection<Integer> teamCals = new LinkedList<Integer>();
final Integer userId = PFUserContext.getUserId();
if (CollectionUtils.isNotEmpty(filter.getTeamCals()) == true) {
for (final Integer calendarId : filter.getTeamCals()) {
final TeamEventSubscription eventSubscription = subscriptions.get(calendarId);
if (eventSubscription == null) {
continue;
}
final TeamCalDO calendar = TeamCalCache.getInstance().getCalendar(calendarId);
if (getTeamCalRight().getAccessType(calendar, userId).isIn(TeamCalAccessType.FULL, TeamCalAccessType.READONLY,
TeamCalAccessType.MINIMAL) == false) {
continue;
}
teamCals.add(calendarId);
}
}
if (filter.getTeamCalId() != null) {
final TeamEventSubscription eventSubscription = subscriptions.get(filter.getTeamCalId());
if (eventSubscription != null) {
final TeamCalDO cal = TeamCalCache.getInstance().getCalendar(filter.getTeamCalId());
if (getTeamCalRight().getAccessType(cal, userId)
.isIn(TeamCalAccessType.FULL, TeamCalAccessType.READONLY, TeamCalAccessType.MINIMAL) == true) {
teamCals.add(filter.getTeamCalId());
}
}
}
if (teamCals != null) {
for (final Integer calendarId : teamCals) {
final TeamEventSubscription eventSubscription = subscriptions.get(calendarId);
if (eventSubscription != null) {
final List<TeamEventDO> recurrenceEvents = eventSubscription.getRecurrenceEvents();
if (recurrenceEvents != null && recurrenceEvents.size() > 0) {
for (final TeamEventDO event : recurrenceEvents) {
final TeamCalDO calendar = TeamCalCache.getInstance().getCalendar(calendarId);
if (getTeamCalRight().getAccessType(calendar, userId) == TeamCalAccessType.MINIMAL) {
result.add(event.createMinimalCopy());
} else {
result.add(event);
}
}
}
}
}
}
return result;
}
private TeamCalAccessType getAccessType(final Integer calendarId, final Integer userId)
{
final TeamCalDO cal = TeamCalCache.getInstance().getCalendar(calendarId);
return getTeamCalRight().getAccessType(cal, userId);
}
/**
* @return the teamCalRight
*/
public TeamCalRight getTeamCalRight()
{
if (teamCalRight == null) {
teamCalRight = (TeamCalRight) UserRights.instance().getRight(TeamCalDao.USER_RIGHT_ID);
}
return teamCalRight;
}
}