mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-07 18:25:23 +00:00
59250: Merged V4.2-BUG-FIX (4.2.1) to HEAD-BUG-FIX (Cloud/4.3) 59188: MNT-9996: All day Calendar events do not show for the first day in the Share Site Calendar grid. Fix unit tests failure. Detect allday events as events with the same start and end dates which have UTC midnight time. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@62114 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
335 lines
14 KiB
Java
335 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2005-2011 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.calendar.cannedqueries;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.TimeZone;
|
|
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.query.CannedQuery;
|
|
import org.alfresco.query.CannedQueryParameters;
|
|
import org.alfresco.query.CannedQuerySortDetails.SortOrder;
|
|
import org.alfresco.repo.calendar.CalendarModel;
|
|
import org.alfresco.repo.domain.query.CannedQueryDAO;
|
|
import org.alfresco.repo.query.AbstractQNameAwareCannedQueryFactory.NestedComparator;
|
|
import org.alfresco.repo.query.AbstractQNameAwareCannedQueryFactory.PropertyBasedComparator;
|
|
import org.alfresco.repo.security.permissions.impl.acegi.AbstractCannedQueryPermissions;
|
|
import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityBean;
|
|
import org.alfresco.service.cmr.calendar.CalendarEntry;
|
|
import org.alfresco.service.cmr.calendar.CalendarRecurrenceHelper;
|
|
import org.alfresco.service.cmr.calendar.CalendarService;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.cmr.tagging.TaggingService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.ISO8601DateFormat;
|
|
import org.alfresco.util.Pair;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
|
/**
|
|
* This class provides support for {@link CannedQuery canned queries} used by the
|
|
* {@link CalendarService}.
|
|
*
|
|
* @author Nick Burch
|
|
* @since 4.0
|
|
*/
|
|
public class GetCalendarEntriesCannedQuery extends AbstractCannedQueryPermissions<CalendarEntry>
|
|
{
|
|
private Log logger = LogFactory.getLog(getClass());
|
|
|
|
private static final String QUERY_NAMESPACE = "alfresco.query.calendar";
|
|
private static final String QUERY_SELECT_GET_BLOGS = "select_GetCalendarEntriesCannedQuery";
|
|
|
|
private final CannedQueryDAO cannedQueryDAO;
|
|
private final TaggingService taggingService;
|
|
private final NodeService nodeService;
|
|
private GetCalendarEntriesCannedQueryTestHook testHook;
|
|
|
|
public GetCalendarEntriesCannedQuery(
|
|
CannedQueryDAO cannedQueryDAO,
|
|
NodeService nodeService,
|
|
TaggingService taggingService,
|
|
MethodSecurityBean<CalendarEntry> methodSecurity,
|
|
CannedQueryParameters params)
|
|
{
|
|
super(params, methodSecurity);
|
|
this.cannedQueryDAO = cannedQueryDAO;
|
|
this.taggingService = taggingService;
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
private boolean isMidnightUTC(String isoDate)
|
|
{
|
|
return isoDate.endsWith("Z") && (isoDate.indexOf("T00:00:00") != -1);
|
|
}
|
|
|
|
@Override
|
|
protected List<CalendarEntry> queryAndFilter(CannedQueryParameters parameters)
|
|
{
|
|
Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null);
|
|
|
|
Object paramBeanObj = parameters.getParameterBean();
|
|
if (paramBeanObj == null)
|
|
throw new NullPointerException("Null GetCalendarEntries query params");
|
|
|
|
GetCalendarEntriesCannedQueryParams paramBean = (GetCalendarEntriesCannedQueryParams) paramBeanObj;
|
|
Date entriesFromDate = paramBean.getEntriesFromDate();
|
|
Date entriesToDate = paramBean.getEntriesToDate();
|
|
|
|
// note: refer to SQL for specific DB filtering (eg.parent nodes etc)
|
|
List<CalendarEntity> results = cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_BLOGS, paramBean, 0, Integer.MAX_VALUE);
|
|
|
|
List<CalendarEntity> filtered = new ArrayList<CalendarEntity>(results.size());
|
|
for (CalendarEntity result : results)
|
|
{
|
|
boolean nextNodeIsAcceptable = true;
|
|
|
|
Date fromDate = null;
|
|
Date toDate = null;
|
|
|
|
String strFromDate = result.getFromDate();
|
|
String strToDate = result.getToDate();
|
|
if (strFromDate != null && strFromDate.equals(strToDate) && isMidnightUTC(strFromDate))
|
|
{
|
|
// it is all day event and should conform with current server's timezone
|
|
fromDate = ISO8601DateFormat.parseDayOnly(strFromDate, TimeZone.getDefault());
|
|
toDate = fromDate;
|
|
}
|
|
else
|
|
{
|
|
fromDate = DefaultTypeConverter.INSTANCE.convert(Date.class, strFromDate);
|
|
toDate = DefaultTypeConverter.INSTANCE.convert(Date.class, strToDate);
|
|
}
|
|
|
|
|
|
if (toDate == null)
|
|
{
|
|
toDate = fromDate;
|
|
}
|
|
|
|
String recurringRule = result.getRecurrenceRule();
|
|
Date recurringLastDate = DefaultTypeConverter.INSTANCE.convert(Date.class, result.getRecurrenceLastMeeting());
|
|
|
|
// Only return entries in the right period
|
|
if (entriesFromDate != null)
|
|
{
|
|
// Needs to end on or after the Filter From date
|
|
if(toDate == null || toDate.before(entriesFromDate))
|
|
{
|
|
nextNodeIsAcceptable = false;
|
|
}
|
|
}
|
|
if (entriesToDate != null)
|
|
{
|
|
// Needs have started by the Filter To date
|
|
if(fromDate == null || fromDate.after(entriesToDate))
|
|
{
|
|
nextNodeIsAcceptable = false;
|
|
}
|
|
}
|
|
|
|
// Handle recurring events specially
|
|
if (recurringRule != null && !nextNodeIsAcceptable)
|
|
{
|
|
if (entriesToDate != null || recurringLastDate != null)
|
|
{
|
|
Date searchFrom = entriesFromDate;
|
|
if (searchFrom == null)
|
|
{
|
|
searchFrom = fromDate;
|
|
}
|
|
Date searchTo = entriesToDate;
|
|
if (searchTo == null)
|
|
{
|
|
searchTo = recurringLastDate;
|
|
}
|
|
|
|
Set<QName> childNodeTypeQNames = new HashSet<QName>();
|
|
childNodeTypeQNames.add(CalendarModel.TYPE_IGNORE_EVENT);
|
|
List<ChildAssociationRef> ignoreEventList = nodeService.getChildAssocs(result.getNodeRef(), childNodeTypeQNames);
|
|
Set<Date> ignoredDates = new HashSet<Date>();
|
|
for (ChildAssociationRef ignoreEvent : ignoreEventList)
|
|
{
|
|
NodeRef nodeRef = ignoreEvent.getChildRef();
|
|
Date ignoredDate = (Date) nodeService.getProperty(nodeRef, CalendarModel.PROP_IGNORE_EVENT_DATE);
|
|
ignoredDates.add(ignoredDate);
|
|
}
|
|
|
|
|
|
List<Date> dates = CalendarRecurrenceHelper.getRecurrencesOnOrAfter(
|
|
recurringRule, fromDate, toDate, recurringLastDate,
|
|
searchFrom, searchTo, false, ignoredDates);
|
|
if (dates != null && dates.size() > 0)
|
|
{
|
|
// Do any of these fit?
|
|
for (Date date : dates)
|
|
{
|
|
if (entriesFromDate != null && entriesToDate != null)
|
|
{
|
|
// From and To date given, needs to sit between them
|
|
if (entriesFromDate.getTime() <= date.getTime() &&
|
|
date.getTime() <= entriesToDate.getTime())
|
|
{
|
|
nextNodeIsAcceptable = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (entriesFromDate != null)
|
|
{
|
|
// From date but no end date, needs to be after the from
|
|
if (entriesFromDate.getTime() <= date.getTime())
|
|
{
|
|
nextNodeIsAcceptable = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (entriesToDate != null)
|
|
{
|
|
// End date but no start date, needs to be before the from
|
|
if (date.getTime() <= entriesToDate.getTime())
|
|
{
|
|
nextNodeIsAcceptable = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Did it make the cut
|
|
if (nextNodeIsAcceptable)
|
|
{
|
|
filtered.add(result);
|
|
}
|
|
}
|
|
|
|
List<Pair<? extends Object, SortOrder>> sortPairs = parameters.getSortDetails().getSortPairs();
|
|
|
|
// For now, the CalendarService only sorts by a single property.
|
|
if (sortPairs != null && !sortPairs.isEmpty())
|
|
{
|
|
List<Pair<Comparator<CalendarEntity>, SortOrder>> comparators =
|
|
new ArrayList<Pair<Comparator<CalendarEntity>,SortOrder>>();
|
|
for (Pair<? extends Object, SortOrder> sortPair : sortPairs)
|
|
{
|
|
final QName sortProperty = (QName)sortPair.getFirst();
|
|
final CalendarEntityComparator comparator = new CalendarEntityComparator(sortProperty);
|
|
comparators.add(new Pair<Comparator<CalendarEntity>, SortOrder>(comparator, sortPair.getSecond()));
|
|
}
|
|
NestedComparator<CalendarEntity> comparator = new NestedComparator<CalendarEntity>(comparators);
|
|
|
|
// Sort
|
|
Collections.sort(filtered, comparator);
|
|
}
|
|
|
|
List<CalendarEntry> calendarEntries = new ArrayList<CalendarEntry>(filtered.size());
|
|
for (CalendarEntity result : filtered)
|
|
{
|
|
calendarEntries.add(new CalendarEntryImpl(result));
|
|
}
|
|
|
|
if (start != null)
|
|
{
|
|
logger.debug("Base query: "+calendarEntries.size()+" in "+(System.currentTimeMillis()-start)+" msecs");
|
|
}
|
|
|
|
if (testHook != null)
|
|
{
|
|
testHook.notifyComplete(results, filtered);
|
|
}
|
|
|
|
return calendarEntries;
|
|
}
|
|
|
|
@Override
|
|
protected boolean isApplyPostQuerySorting()
|
|
{
|
|
// No post-query sorting. It's done within the queryAndFilter() method above.
|
|
return false;
|
|
}
|
|
|
|
public void setTestHook(GetCalendarEntriesCannedQueryTestHook hook)
|
|
{
|
|
this.testHook = hook;
|
|
}
|
|
|
|
private class CalendarEntryImpl extends org.alfresco.repo.calendar.CalendarEntryImpl
|
|
{
|
|
private static final long serialVersionUID = 5717119409619436964L;
|
|
private CalendarEntryImpl(CalendarEntity entity)
|
|
{
|
|
super(entity.getNodeRef(),
|
|
// TODO Fetch this from the database layer when querying
|
|
nodeService.getPrimaryParent(entity.getNodeRef()).getParentRef(),
|
|
entity.getName());
|
|
super.populate(nodeService.getProperties(entity.getNodeRef()));
|
|
super.setTags(taggingService.getTags(entity.getNodeRef()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility class to sort {@link CalendarEntry}s on the basis of a Comparable property.
|
|
* Comparisons of two null properties are considered 'equal' by this comparator.
|
|
* Comparisons involving one null and one non-null property will return the null property as
|
|
* being 'before' the non-null property.
|
|
*
|
|
* Note that it is the responsibility of the calling code to ensure that the specified
|
|
* property values actually implement Comparable themselves.
|
|
*/
|
|
protected static class CalendarEntityComparator extends PropertyBasedComparator<CalendarEntity>
|
|
{
|
|
protected CalendarEntityComparator(QName property)
|
|
{
|
|
super(property);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
protected Comparable getProperty(CalendarEntity entity)
|
|
{
|
|
if (comparableProperty.equals(CalendarModel.PROP_FROM_DATE))
|
|
{
|
|
return entity.getFromDate();
|
|
}
|
|
else if (comparableProperty.equals(CalendarModel.PROP_TO_DATE))
|
|
{
|
|
return entity.getToDate();
|
|
}
|
|
else if (comparableProperty.equals(ContentModel.PROP_CREATED))
|
|
{
|
|
return entity.getCreatedDate();
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException("Unsupported calendar sort property: "+comparableProperty);
|
|
}
|
|
}
|
|
}
|
|
}
|