/** * 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 static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.google.common.base.Function; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.Callable; import javax.naming.CompositeName; import org.apache.commons.lang.mutable.MutableInt; import org.apache.commons.lang.mutable.MutableObject; import org.apereo.portal.concurrency.CallableWithoutResult; import org.apereo.portal.concurrency.FunctionWithoutResult; import org.apereo.portal.events.aggr.dao.DateDimensionDao; import org.apereo.portal.events.aggr.dao.TimeDimensionDao; import org.apereo.portal.events.aggr.groups.AggregatedGroupLookupDao; import org.apereo.portal.events.aggr.groups.AggregatedGroupMapping; import org.apereo.portal.groups.ICompositeGroupService; import org.apereo.portal.groups.IEntityGroup; import org.apereo.portal.test.BaseAggrEventsJpaDaoTest; import org.apereo.portal.utils.Tuple; import org.joda.time.DateMidnight; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalTime; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; /** */ public abstract class JpaBaseAggregationDaoTest< T extends BaseAggregationImpl<K, D>, K extends BaseAggregationKey, D extends BaseGroupedAggregationDiscriminator> extends BaseAggrEventsJpaDaoTest { @Autowired protected TimeDimensionDao timeDimensionDao; @Autowired protected DateDimensionDao dateDimensionDao; @Autowired protected AggregatedGroupLookupDao aggregatedGroupLookupDao; @Autowired protected ICompositeGroupService compositeGroupService; @Autowired protected AggregationIntervalHelper aggregationIntervalHelper; /** @return The private aggregation DAO to use */ protected abstract BaseAggregationPrivateDao<T, K> getAggregationDao(); /** * Populate some data in the aggregation, use the Random source if needed for the population (it * will be consistent across test runs) */ protected abstract void updateAggregation( AggregationIntervalInfo intervalInfo, T aggregation, Random r); /** Create an aggregation key */ protected abstract K createAggregationKey( AggregationIntervalInfo intervalInfo, AggregatedGroupMapping aggregatedGroup); /** Create an aggregation key */ protected abstract K createAggregationKey( AggregationInterval interval, AggregatedGroupMapping aggregatedGroup); /** Create a list of aggregations for use in the lifecycle test */ protected abstract Map<K, T> createAggregations( AggregationIntervalInfo intervalInfo, AggregatedGroupMapping aggregatedGroup); @Test public final void testBaseAggregationLifecycle() throws Exception { final IEntityGroup entityGroupA = mock(IEntityGroup.class); when(entityGroupA.getServiceName()).thenReturn(new CompositeName("local")); when(entityGroupA.getName()).thenReturn("Group A"); when(compositeGroupService.findGroup("local.0")).thenReturn(entityGroupA); final IEntityGroup entityGroupB = mock(IEntityGroup.class); when(entityGroupB.getServiceName()).thenReturn(new CompositeName("local")); when(entityGroupB.getName()).thenReturn("Group B"); when(compositeGroupService.findGroup("local.1")).thenReturn(entityGroupB); final DateTime instant = new DateTime(1326734644000l, DateTimeZone.UTC); //just a random time //Create required date and time dimensions populateDateTimeDimensions(instant.minusHours(2), instant.plusHours(2), null); //Create aggregations final Map<K, T> createdAggrs = this.executeInTransaction( new Callable<Map<K, T>>() { @Override public Map<K, T> call() throws Exception { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final AggregationIntervalInfo fiveMinuteInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.FIVE_MINUTE, instant); final AggregationIntervalInfo hourInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.HOUR, instant); final Map<K, T> fiveMinGroupA = createAggregations(fiveMinuteInfo, groupA); final Map<K, T> fiveMinGroupB = createAggregations(fiveMinuteInfo, groupB); final Map<K, T> hourGroupA = createAggregations(hourInfo, groupA); final Map<K, T> aggrs = new HashMap<K, T>(fiveMinGroupA); aggrs.putAll(fiveMinGroupB); aggrs.putAll(hourGroupA); return aggrs; } }); //Verify aggregations were created this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregationIntervalInfo fiveMinuteInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.FIVE_MINUTE, instant); final AggregationIntervalInfo hourInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.HOUR, instant); final Map<K, T> fiveMinGroup = getAggregationDao() .getAggregationsForInterval( fiveMinuteInfo.getDateDimension(), fiveMinuteInfo.getTimeDimension(), fiveMinuteInfo.getAggregationInterval()); final Map<K, T> hourGroup = getAggregationDao() .getAggregationsForInterval( hourInfo.getDateDimension(), hourInfo.getTimeDimension(), hourInfo.getAggregationInterval()); final Map<K, T> foundAggrs = new HashMap<K, T>(fiveMinGroup); foundAggrs.putAll(hourGroup); assertEquals( "Aggregations not created as expected", createdAggrs, foundAggrs); } }); //Update Aggregations final Map<K, T> updatedAggrs = this.executeInTransaction( new Callable<Map<K, T>>() { @Override public Map<K, T> call() throws Exception { final Random r = new Random(0); final AggregationIntervalInfo fiveMinuteInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.FIVE_MINUTE, instant); final AggregationIntervalInfo hourInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.HOUR, instant); final Map<K, T> fiveMinGroup = getAggregationDao() .getAggregationsForInterval( fiveMinuteInfo.getDateDimension(), fiveMinuteInfo.getTimeDimension(), fiveMinuteInfo.getAggregationInterval()); final Map<K, T> hourGroup = getAggregationDao() .getAggregationsForInterval( hourInfo.getDateDimension(), hourInfo.getTimeDimension(), hourInfo.getAggregationInterval()); final Map<K, T> updatedAggrs = new HashMap<K, T>(); for (final Entry<K, T> aggrEntry : fiveMinGroup.entrySet()) { final T aggr = aggrEntry.getValue(); updateAggregation(fiveMinuteInfo, aggr, r); getAggregationDao().updateAggregation(aggr); updatedAggrs.put(aggrEntry.getKey(), aggr); } for (final Entry<K, T> aggrEntry : hourGroup.entrySet()) { final T aggr = aggrEntry.getValue(); updateAggregation(hourInfo, aggr, r); getAggregationDao().updateAggregation(aggr); updatedAggrs.put(aggrEntry.getKey(), aggr); } return updatedAggrs; } }); //Verify aggregations were updated this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregationIntervalInfo fiveMinuteInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.FIVE_MINUTE, instant); final AggregationIntervalInfo hourInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.HOUR, instant); final Map<K, T> fiveMinGroup = getAggregationDao() .getAggregationsForInterval( fiveMinuteInfo.getDateDimension(), fiveMinuteInfo.getTimeDimension(), fiveMinuteInfo.getAggregationInterval()); final Map<K, T> hourGroup = getAggregationDao() .getAggregationsForInterval( hourInfo.getDateDimension(), hourInfo.getTimeDimension(), hourInfo.getAggregationInterval()); final Map<K, T> foundAggrs = new HashMap<K, T>(fiveMinGroup); foundAggrs.putAll(hourGroup); assertEquals( "Aggregations not updated as expected", updatedAggrs, foundAggrs); } }); //Complete intervals final Map<K, T> completeAggrs = this.executeInTransaction( new Callable<Map<K, T>>() { @Override public Map<K, T> call() throws Exception { final AggregationIntervalInfo fiveMinuteInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.FIVE_MINUTE, instant); final AggregationIntervalInfo hourInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.HOUR, instant); final Map<K, T> fiveMinGroup = getAggregationDao() .getAggregationsForInterval( fiveMinuteInfo.getDateDimension(), fiveMinuteInfo.getTimeDimension(), fiveMinuteInfo.getAggregationInterval()); final Map<K, T> hourGroup = getAggregationDao() .getAggregationsForInterval( hourInfo.getDateDimension(), hourInfo.getTimeDimension(), hourInfo.getAggregationInterval()); final Map<K, T> completeAggrs = new HashMap<K, T>(); for (final Entry<K, T> aggrEntry : fiveMinGroup.entrySet()) { final T aggr = aggrEntry.getValue(); aggr.intervalComplete(5); getAggregationDao().updateAggregation(aggr); completeAggrs.put(aggrEntry.getKey(), aggr); } for (final Entry<K, T> aggrEntry : hourGroup.entrySet()) { final T aggr = aggrEntry.getValue(); aggr.intervalComplete(60); getAggregationDao().updateAggregation(aggr); completeAggrs.put(aggrEntry.getKey(), aggr); } return completeAggrs; } }); //Verify aggregations were completed this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregationIntervalInfo fiveMinuteInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.FIVE_MINUTE, instant); final AggregationIntervalInfo hourInfo = aggregationIntervalHelper.getIntervalInfo( AggregationInterval.HOUR, instant); final Map<K, T> fiveMinGroup = getAggregationDao() .getAggregationsForInterval( fiveMinuteInfo.getDateDimension(), fiveMinuteInfo.getTimeDimension(), fiveMinuteInfo.getAggregationInterval()); final Map<K, T> hourGroup = getAggregationDao() .getAggregationsForInterval( hourInfo.getDateDimension(), hourInfo.getTimeDimension(), hourInfo.getAggregationInterval()); final Map<K, T> foundAggrs = new HashMap<K, T>(fiveMinGroup); foundAggrs.putAll(hourGroup); assertEquals( "Aggregations not completed as expected", completeAggrs, foundAggrs); } }); } @Test public final void testBaseAggregationRangeQuery() throws Exception { final IEntityGroup entityGroupA = mock(IEntityGroup.class); when(entityGroupA.getServiceName()).thenReturn(new CompositeName("local")); when(entityGroupA.getName()).thenReturn("Group A"); when(compositeGroupService.findGroup("local.0")).thenReturn(entityGroupA); final IEntityGroup entityGroupB = mock(IEntityGroup.class); when(entityGroupB.getServiceName()).thenReturn(new CompositeName("local")); when(entityGroupB.getName()).thenReturn("Group B"); when(compositeGroupService.findGroup("local.1")).thenReturn(entityGroupB); final MutableInt aggrs = new MutableInt(); //Create 2 days of login aggregates ... every 5 minutes final DateTime start = new DateTime(1326734644000l, DateTimeZone.UTC).minuteOfDay().roundFloorCopy(); final DateTime end = start.plusDays(2); final AggregationInterval interval = AggregationInterval.FIVE_MINUTE; final MutableObject startObj = new MutableObject(); final MutableObject endObj = new MutableObject(); this.executeInTransaction( new CallableWithoutResult() { @Override protected void callWithoutResult() { final Random r = new Random(0); final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); populateDateTimeDimensions( start, end, new FunctionWithoutResult<Tuple<DateDimension, TimeDimension>>() { @Override protected void applyWithoutResult( Tuple<DateDimension, TimeDimension> input) { final TimeDimension td = input.second; final DateDimension dd = input.first; final DateTime instant = td.getTime().toDateTime(dd.getDate()); if (startObj.getValue() == null) { startObj.setValue(instant); } endObj.setValue(instant); if (instant.equals(interval.determineStart(instant))) { final AggregationIntervalInfo intervalInfo = aggregationIntervalHelper.getIntervalInfo( interval, instant); final T baseAggregationA = getAggregationDao() .createAggregation( createAggregationKey( intervalInfo, groupA)); final T baseAggregationB = getAggregationDao() .createAggregation( createAggregationKey( intervalInfo, groupB)); for (int u = 0; u < r.nextInt(50); u++) { updateAggregation( intervalInfo, baseAggregationA, r); updateAggregation( intervalInfo, baseAggregationB, r); } baseAggregationA.intervalComplete(5); baseAggregationB.intervalComplete(5); getAggregationDao().updateAggregation(baseAggregationA); getAggregationDao().updateAggregation(baseAggregationB); aggrs.add(2); } } }); } }); //Verify all aggrs created assertEquals(1152, aggrs.intValue()); //Find aggrs for one day this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final DateTime queryStart = start.toDateMidnight().toDateTime(); final DateTime queryEnd = queryStart.plusDays(1).minusSeconds(1); final List<T> baseAggregations = getAggregationDao() .getAggregations( queryStart, queryEnd, createAggregationKey(interval, groupA), groupB); assertEquals(158, baseAggregations.size()); } }); //Find aggrs for second day this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final DateTime queryStart = start.toDateMidnight().minusDays(1).toDateTime(); final DateTime queryEnd = queryStart.plusDays(2).minusSeconds(1); final List<T> baseAggregations = getAggregationDao() .getAggregations( queryStart, queryEnd, createAggregationKey(interval, groupA), groupB); assertEquals(158, baseAggregations.size()); } }); //Find all aggrs this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final List<T> baseAggregations = getAggregationDao() .getAggregations( start, end.plusDays(1), createAggregationKey(interval, groupA), groupB); assertEquals(1152, baseAggregations.size()); } }); //Find first days worth this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final List<T> baseAggregations = getAggregationDao() .getAggregations( start, end, createAggregationKey(interval, groupA), groupB); assertEquals(1152, baseAggregations.size()); } }); //Find second days worth this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final List<T> baseAggregations = getAggregationDao() .getAggregations( start.plusDays(1), end.plusDays(1), createAggregationKey(interval, groupA), groupB); assertEquals(576, baseAggregations.size()); } }); //Find first 12 hours worth this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final List<T> baseAggregations = getAggregationDao() .getAggregations( start, start.plusHours(12), createAggregationKey(interval, groupA), groupB); assertEquals(288, baseAggregations.size()); } }); //Find middle 24 hours worth this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final List<T> baseAggregations = getAggregationDao() .getAggregations( start.plusHours(12), end.plusHours(12), createAggregationKey(interval, groupA), groupB); assertEquals(864, baseAggregations.size()); } }); //Find middle 24 hours worth for one group this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final List<T> baseAggregations = getAggregationDao() .getAggregations( start.plusHours(12), end.plusHours(12), createAggregationKey(interval, groupA)); assertEquals(432, baseAggregations.size()); } }); //Find last 12 hours worth this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); final List<T> baseAggregations = getAggregationDao() .getAggregations( start.plusHours(36), end.plusDays(1), createAggregationKey(interval, groupA), groupB); assertEquals(288, baseAggregations.size()); } }); //TODO Query for intervals that are stored this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final Set<AggregatedGroupMapping> aggregatedGroupMappings = getAggregationDao().getAggregatedGroupMappings(); assertEquals(2, aggregatedGroupMappings.size()); final Set<AggregationInterval> aggregationIntervals = getAggregationDao().getAggregationIntervals(); assertEquals(1, aggregationIntervals.size()); } }); } @Test public final void testModifyingClosedAggregationRangeQuery() throws Exception { final IEntityGroup entityGroupA = mock(IEntityGroup.class); when(entityGroupA.getServiceName()).thenReturn(new CompositeName("local")); when(entityGroupA.getName()).thenReturn("Group A"); when(compositeGroupService.findGroup("local.0")).thenReturn(entityGroupA); final MutableInt aggrs = new MutableInt(); //Create 10 minutes of aggregations final DateTime start = new DateTime(1326734644000l, DateTimeZone.UTC).minuteOfDay().roundFloorCopy(); final DateTime end = start.plusMinutes(10); final AggregationInterval interval = AggregationInterval.FIVE_MINUTE; final MutableObject startObj = new MutableObject(); final MutableObject endObj = new MutableObject(); this.executeInTransaction( new CallableWithoutResult() { @Override protected void callWithoutResult() { final Random r = new Random(0); final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); populateDateTimeDimensions( start, end, new FunctionWithoutResult<Tuple<DateDimension, TimeDimension>>() { @Override protected void applyWithoutResult( Tuple<DateDimension, TimeDimension> input) { final TimeDimension td = input.second; final DateDimension dd = input.first; final DateTime instant = td.getTime().toDateTime(dd.getDate()); if (startObj.getValue() == null) { startObj.setValue(instant); } endObj.setValue(instant); if (instant.equals(interval.determineStart(instant))) { final AggregationIntervalInfo intervalInfo = aggregationIntervalHelper.getIntervalInfo( interval, instant); final T baseAggregationA = getAggregationDao() .createAggregation( createAggregationKey( intervalInfo, groupA)); for (int u = 0; u < r.nextInt(50); u++) { updateAggregation( intervalInfo, baseAggregationA, r); } baseAggregationA.intervalComplete(5); getAggregationDao().updateAggregation(baseAggregationA); aggrs.add(1); } } }); } }); //Verify all aggrs created assertEquals(2, aggrs.intValue()); //Find unclosed 1 aggr this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final Random r = new Random(0); final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final K key = createAggregationKey(interval, groupA); final List<T> aggregations = getAggregationDao() .getAggregations(start.minusDays(1), end.plusDays(1), key); assertEquals(2, aggregations.size()); for (final T baseAggregationImpl : aggregations) { final DateTime instant = baseAggregationImpl.getDateTime(); final AggregationIntervalInfo intervalInfo = aggregationIntervalHelper.getIntervalInfo(interval, instant); updateAggregation(intervalInfo, baseAggregationImpl, r); //TODO verify unchanged } } }); } @Test public final void testUnclosedBaseAggregationRangeQuery() throws Exception { final IEntityGroup entityGroupA = mock(IEntityGroup.class); when(entityGroupA.getServiceName()).thenReturn(new CompositeName("local")); when(entityGroupA.getName()).thenReturn("Group A"); when(compositeGroupService.findGroup("local.0")).thenReturn(entityGroupA); final IEntityGroup entityGroupB = mock(IEntityGroup.class); when(entityGroupB.getServiceName()).thenReturn(new CompositeName("local")); when(entityGroupB.getName()).thenReturn("Group B"); when(compositeGroupService.findGroup("local.1")).thenReturn(entityGroupB); final MutableInt aggrs = new MutableInt(); //Create 10 minutes of aggregations final DateTime start = new DateTime(1326734644000l, DateTimeZone.UTC).minuteOfDay().roundFloorCopy(); final DateTime end = start.plusMinutes(10); final AggregationInterval interval = AggregationInterval.FIVE_MINUTE; final MutableObject startObj = new MutableObject(); final MutableObject endObj = new MutableObject(); this.executeInTransaction( new CallableWithoutResult() { @Override protected void callWithoutResult() { final Random r = new Random(0); final AggregatedGroupMapping groupA = aggregatedGroupLookupDao.getGroupMapping("local.0"); final AggregatedGroupMapping groupB = aggregatedGroupLookupDao.getGroupMapping("local.1"); populateDateTimeDimensions( start, end, new FunctionWithoutResult<Tuple<DateDimension, TimeDimension>>() { @Override protected void applyWithoutResult( Tuple<DateDimension, TimeDimension> input) { final TimeDimension td = input.second; final DateDimension dd = input.first; final DateTime instant = td.getTime().toDateTime(dd.getDate()); if (startObj.getValue() == null) { startObj.setValue(instant); } endObj.setValue(instant); if (instant.equals(interval.determineStart(instant))) { final AggregationIntervalInfo intervalInfo = aggregationIntervalHelper.getIntervalInfo( interval, instant); final T baseAggregationA = getAggregationDao() .createAggregation( createAggregationKey( intervalInfo, groupA)); final T baseAggregationB = getAggregationDao() .createAggregation( createAggregationKey( intervalInfo, groupB)); for (int u = 0; u < r.nextInt(50); u++) { updateAggregation( intervalInfo, baseAggregationA, r); updateAggregation( intervalInfo, baseAggregationB, r); } if (aggrs.intValue() % 4 == 0) { baseAggregationA.intervalComplete(5); } baseAggregationB.intervalComplete(5); getAggregationDao().updateAggregation(baseAggregationA); getAggregationDao().updateAggregation(baseAggregationB); aggrs.add(2); } } }); } }); //Verify all aggrs created assertEquals(4, aggrs.intValue()); //Find unclosed 1 aggr this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final Collection<T> baseAggregations = getAggregationDao() .getUnclosedAggregations( start.minusDays(1), end.plusDays(1), interval); assertEquals(1, baseAggregations.size()); for (final T baseAggregationImpl : baseAggregations) { baseAggregationImpl.intervalComplete(5); getAggregationDao().updateAggregation(baseAggregationImpl); } } }); //Find unclosed 0 aggr this.execute( new CallableWithoutResult() { @Override protected void callWithoutResult() { final Collection<T> baseAggregations = getAggregationDao() .getUnclosedAggregations( start.minusDays(1), end.plusDays(1), interval); assertEquals(0, baseAggregations.size()); } }); } /** Populate date & time dimensions in an interval range executing a callback for each pair */ public final <RT> List<RT> populateDateTimeDimensions( final DateTime start, final DateTime end, final Function<Tuple<DateDimension, TimeDimension>, RT> newDimensionHandler) { return this.executeInTransaction( new Callable<List<RT>>() { @Override public List<RT> call() throws Exception { final List<RT> results = new LinkedList<RT>(); final SortedMap<LocalTime, TimeDimension> times = new TreeMap<LocalTime, TimeDimension>(); final SortedMap<DateMidnight, DateDimension> dates = new TreeMap<DateMidnight, DateDimension>(); DateTime nextDateTime = start.minuteOfDay().roundFloorCopy(); while (nextDateTime.isBefore(end)) { //get/create TimeDimension final LocalTime localTime = nextDateTime.toLocalTime(); TimeDimension td = times.get(localTime); if (td == null) { td = timeDimensionDao.createTimeDimension(localTime); times.put(localTime, td); } //get/create DateDimension final DateMidnight dateMidnight = nextDateTime.toDateMidnight(); DateDimension dd = dates.get(dateMidnight); if (dd == null) { dd = dateDimensionDao.createDateDimension(dateMidnight, 0, null); dates.put(dateMidnight, dd); } //Let callback do work if (newDimensionHandler != null) { final RT result = newDimensionHandler.apply( new Tuple<DateDimension, TimeDimension>(dd, td)); if (result != null) { results.add(result); } } nextDateTime = nextDateTime.plusMinutes(1); } return results; } }); } }