/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.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.apereo.portal.events.aggr.dao.DateDimensionDao;
import org.apereo.portal.events.aggr.dao.IEventAggregationManagementDao;
import org.apereo.portal.events.aggr.dao.TimeDimensionDao;
import org.apereo.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;
/**
*/
@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);
}
}
}