/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.portal.events.aggr;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.events.aggr.dao.DateDimensionDao;
import org.jasig.portal.events.aggr.dao.IEventAggregationManagementDao;
import org.jasig.portal.events.aggr.dao.TimeDimensionDao;
import org.jasig.portal.events.aggr.dao.jpa.AcademicTermDetailImpl;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author Eric Dalquist
* @version $Revision: 18025 $
*/
@Service
public class AggregationIntervalHelperImpl implements AggregationIntervalHelper {
protected final Log logger = LogFactory.getLog(this.getClass());
private TimeDimensionDao timeDimensionDao;
private DateDimensionDao dateDimensionDao;
private IEventAggregationManagementDao eventAggregationManagementDao;
@Autowired
public void setEventAggregationManagementDao(IEventAggregationManagementDao eventAggregationManagementDao) {
this.eventAggregationManagementDao = eventAggregationManagementDao;
}
@Autowired
public void setTimeDimensionDao(TimeDimensionDao timeDimensionDao) {
this.timeDimensionDao = timeDimensionDao;
}
@Autowired
public void setDateDimensionDao(DateDimensionDao dateDimensionDao) {
this.dateDimensionDao = dateDimensionDao;
}
@Override
public List<DateTime> getIntervalStartDateTimesBetween(AggregationInterval interval, DateTime start, DateTime end) {
return getIntervalStartDateTimesBetween(interval, start, end, -1);
}
public int intervalsBetween(AggregationInterval interval, DateTime start, DateTime end) {
//For intervals that support native determination
if (interval.isSupportsDetermination()) {
return interval.determineIntervalsBetween(start, end);
}
//Special handling for intervals that don't support determination
if (interval == AggregationInterval.ACADEMIC_TERM) {
//Since terms can have gaps all terms must be checked
//Find the first term than is in the range via binary search
final List<AcademicTermDetail> terms = getAcademicTermsAfter(start);
//Count all the terms that are in the range, breaking the loop on the first non-matching term
int count = 0;
for (final AcademicTermDetail academicTerm : terms) {
final DateMidnight termStart = academicTerm.getStart();
if (end.isAfter(termStart) && !start.isAfter(termStart)) {
count++;
}
else if (count > 0) {
//getAcademicTermDetails returns the list in order, after at least one match has been found
//a term that doesn't match means no more matches will be found
break;
}
}
return count;
}
//Fallback for any other interval type that needs explicit iteration
AggregationIntervalInfo nextInterval = this.getIntervalInfo(interval, start);
int count = 0;
while (nextInterval.getStart().isBefore(end)) {
//Needed to make sure we don't count a partial first interval
if (!start.isAfter(nextInterval.getStart())) {
count++;
}
nextInterval = this.getIntervalInfo(interval, nextInterval.getEnd());
}
return count;
}
@Override
public List<DateTime> getIntervalStartDateTimesBetween(AggregationInterval interval, DateTime start, DateTime end, int maxTimes) {
if (interval.isSupportsDetermination()) {
//Get the interval count for the date-time field type and verify it is in the valid range.
final int intervals = interval.determineIntervalsBetween(start, end);
verifyIntervalCount(start, end, maxTimes, intervals);
//Result list
final List<DateTime> result = new ArrayList<DateTime>(intervals);
//Check if first interval in the range
DateTime intervalStart = interval.determineStart(start);
if (!intervalStart.isBefore(start)) {
result.add(intervalStart);
}
//Move one step forward in the range
DateTime intervalEnd = interval.determineEnd(intervalStart);
intervalStart = interval.determineStart(intervalEnd);
//Step through the interval start/end values to build the full list
while (intervalStart.isBefore(end)) {
result.add(intervalStart);
intervalEnd = interval.determineEnd(intervalStart);
intervalStart = interval.determineStart(intervalEnd);
}
return result;
}
//Special handling for intervals that don't support determination
if (interval == AggregationInterval.ACADEMIC_TERM) {
//Since terms can have gaps all terms must be checked
final List<AcademicTermDetail> terms = getAcademicTermsAfter(start);
//Use all the terms that are in the range, breaking the loop on the first non-matching term
final List<DateTime> result = new ArrayList<DateTime>(terms.size());
for (final AcademicTermDetail academicTerm : terms) {
final DateMidnight termStart = academicTerm.getStart();
if (end.isAfter(termStart) && !start.isAfter(termStart)) {
result.add(start);
}
else if (!result.isEmpty()) {
//getAcademicTermDetails returns the list in order, after at least one match has been found
//a term that doesn't match means no more matches will be found
break;
}
}
return result;
}
//Fallback for any other interval type that needs explicit iteration
AggregationIntervalInfo nextInterval = this.getIntervalInfo(interval, start);
final List<DateTime> result = new ArrayList<DateTime>();
while (nextInterval.getStart().isBefore(end)) {
//Needed to make sure we don't count a partial first interval
if (!start.isAfter(nextInterval.getStart())) {
result.add(nextInterval.getStart());
if (maxTimes > 0 && result.size() > maxTimes) {
throw new IllegalArgumentException("There more than " + result.size() + " intervals between " + start + " and " + end + " which is more than the specified maximum of " + maxTimes);
}
}
nextInterval = this.getIntervalInfo(interval, nextInterval.getEnd());
}
return result;
}
@Override
public AggregationIntervalInfo getIntervalInfo(AggregationInterval interval, DateTime date) {
//Chop off everything below the minutes (seconds, millis)
final DateTime instant = date.minuteOfHour().roundFloorCopy();
final DateTime start, end;
switch (interval) {
case CALENDAR_QUARTER: {
final List<QuarterDetail> quartersDetails = this.eventAggregationManagementDao.getQuartersDetails();
final QuarterDetail quarterDetail = EventDateTimeUtils.findDateRangeSorted(instant, quartersDetails);
start = quarterDetail.getStartDateMidnight(date).toDateTime();
end = quarterDetail.getEndDateMidnight(date).toDateTime();
break;
}
case ACADEMIC_TERM: {
final List<AcademicTermDetail> academicTermDetails = this.eventAggregationManagementDao.getAcademicTermDetails();
final AcademicTermDetail academicTermDetail = EventDateTimeUtils.findDateRangeSorted(date, academicTermDetails);
if (academicTermDetail == null) {
return null;
}
start = academicTermDetail.getStart().toDateTime();
end = academicTermDetail.getEnd().toDateTime();
break;
}
default: {
start = interval.determineStart(instant);
end = interval.determineEnd(start);
}
}
final LocalTime startTime = start.toLocalTime();
final TimeDimension startTimeDimension = this.timeDimensionDao.getTimeDimensionByTime(startTime);
final DateMidnight startDateMidnight = start.toDateMidnight();
final DateDimension startDateDimension = this.dateDimensionDao.getDateDimensionByDate(startDateMidnight);
return new AggregationIntervalInfo(interval, start, end, startDateDimension, startTimeDimension);
}
/**
* Return a sorted list of AcademicTermDetail objects where the the first element of the list where the first element
* is the first term that starts after the specified start DateTime.
*/
protected List<AcademicTermDetail> getAcademicTermsAfter(DateTime start) {
final List<AcademicTermDetail> terms = this.eventAggregationManagementDao.getAcademicTermDetails();
final int index = Collections.binarySearch(terms, new AcademicTermDetailImpl(start.toDateMidnight(), start.plusDays(1).toDateMidnight(), ""));
if (index > 0) {
return terms.subList(index, terms.size());
}
else if (index < 0) {
return terms.subList(-(index + 1), terms.size());
}
return terms;
}
protected void verifyIntervalCount(DateTime start, DateTime end,
int maxTimes, final int intervals) {
if (maxTimes > 0 && intervals > maxTimes) {
throw new IllegalArgumentException("There are " + intervals + " intervals between " + start + " and " + end + " which is more than the specified maximum of " + maxTimes);
}
}
}