org.jasig.portal.events.aggr.PortalEventAggregationManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.portal.events.aggr.PortalEventAggregationManagerImpl.java

Source

/**
 * 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.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang.mutable.MutableInt;
import org.jasig.portal.IPortalInfoProvider;
import org.jasig.portal.concurrency.FunctionWithoutResult;
import org.jasig.portal.concurrency.locking.IClusterLockService;
import org.jasig.portal.concurrency.locking.IClusterLockService.TryLockFunctionResult;
import org.jasig.portal.events.LoginEvent;
import org.jasig.portal.events.PortalEvent;
import org.jasig.portal.events.aggr.IEventAggregatorStatus.ProcessingType;
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.session.EventSession;
import org.jasig.portal.events.aggr.session.EventSessionDao;
import org.jasig.portal.events.handlers.db.IPortalEventDao;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalTime;
import org.joda.time.Period;
import org.joda.time.ReadablePeriod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;

/**
 * @author Eric Dalquist
 * @version $Revision$
 */
@Service("portalEventAggregationManager")
public class PortalEventAggregationManagerImpl implements IPortalEventAggregationManager {
    private static final String DIMENSION_LOCK_NAME = PortalEventAggregationManagerImpl.class.getName()
            + ".DIMENSION_LOCK";
    private static final String AGGREGATION_LOCK_NAME = PortalEventAggregationManagerImpl.class.getName()
            + ".AGGREGATION_LOCK";
    private static final String PURGE_RAW_EVENTS_LOCK_NAME = PortalEventAggregationManagerImpl.class.getName()
            + ".PURGE_RAW_EVENTS_LOCK";
    private static final String PURGE_EVENT_SESSION_LOCK_NAME = PortalEventAggregationManagerImpl.class.getName()
            + ".PURGE_EVENT_SESSION_LOCK_NAME";

    protected final Logger logger = LoggerFactory.getLogger(getClass());
    private final AtomicBoolean checkedDimensions = new AtomicBoolean(false);

    private IPortalInfoProvider portalInfoProvider;
    private IClusterLockService clusterLockService;
    private IEventAggregationManagementDao eventAggregationManagementDao;
    private IPortalEventDao portalEventDao;
    private TimeDimensionDao timeDimensionDao;
    private DateDimensionDao dateDimensionDao;
    private AggregationIntervalHelper intervalHelper;
    private EventSessionDao eventSessionDao;
    private Set<IPortalEventAggregator<PortalEvent>> portalEventAggregators;
    private TransactionOperations aggrEventsTransactionOperations;

    private int eventAggregationBatchSize = 5000;
    private ReadablePeriod aggregationDelay = Period.seconds(30);
    private ReadablePeriod purgeDelay = Period.days(1);
    private ReadablePeriod dimensionBuffer = Period.days(30);

    @Autowired
    public void setAggrEventsPlatformTransactionManager(
            @Qualifier("aggrEvents") PlatformTransactionManager transactionManager) {
        final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.afterPropertiesSet();
        this.aggrEventsTransactionOperations = transactionTemplate;
    }

    @Autowired
    public void setEventSessionDao(EventSessionDao eventSessionDao) {
        this.eventSessionDao = eventSessionDao;
    }

    @Autowired
    public void setIntervalHelper(AggregationIntervalHelper intervalHelper) {
        this.intervalHelper = intervalHelper;
    }

    @Autowired
    public void setTimeDimensionDao(TimeDimensionDao timeDimensionDao) {
        this.timeDimensionDao = timeDimensionDao;
    }

    @Autowired
    public void setDateDimensionDao(DateDimensionDao dateDimensionDao) {
        this.dateDimensionDao = dateDimensionDao;
    }

    @Autowired
    public void setPortalInfoProvider(IPortalInfoProvider portalInfoProvider) {
        this.portalInfoProvider = portalInfoProvider;
    }

    @Autowired
    public void setEventAggregationManagementDao(IEventAggregationManagementDao eventAggregationManagementDao) {
        this.eventAggregationManagementDao = eventAggregationManagementDao;
    }

    @Autowired
    public void setClusterLockService(IClusterLockService clusterLockService) {
        this.clusterLockService = clusterLockService;
    }

    @Autowired
    public void setPortalEventDao(IPortalEventDao portalEventDao) {
        this.portalEventDao = portalEventDao;
    }

    @Autowired
    public void setPortalEventAggregators(Set<IPortalEventAggregator<PortalEvent>> portalEventAggregators) {
        this.portalEventAggregators = portalEventAggregators;
    }

    @Value("${org.jasig.portal.event.aggr.PortalEventAggregationManager.aggregationDelay:PT30S}")
    public void setAggregationDelay(ReadablePeriod aggregationDelay) {
        this.aggregationDelay = aggregationDelay;
    }

    @Value("${org.jasig.portal.event.aggr.PortalEventAggregationManager.purgeDelay:P1D}")
    public void setPurgeDelay(ReadablePeriod purgeDelay) {
        this.purgeDelay = purgeDelay;
    }

    @Value("${org.jasig.portal.event.aggr.PortalEventAggregationManager.eventAggregationBatchSize:5000}")
    public void setEventAggregationBatchSize(int eventAggregationBatchSize) {
        this.eventAggregationBatchSize = eventAggregationBatchSize;
    }

    @Value("${org.jasig.portal.event.aggr.PortalEventAggregationManager.dimensionBuffer:P30D}")
    public void setDimensionBuffer(ReadablePeriod dimensionBuffer) {
        if (new Period(dimensionBuffer).toStandardDays().getDays() < 1) {
            throw new IllegalArgumentException("dimensionBuffer must be at least 1 day. Is: "
                    + new Period(dimensionBuffer).toStandardDays().getDays());
        }
        this.dimensionBuffer = dimensionBuffer;
    }

    @Override
    @Transactional(value = "aggrEventsTransactionManager")
    public boolean populateDimensions() {
        try {
            final TryLockFunctionResult<Object> result = this.clusterLockService.doInTryLock(DIMENSION_LOCK_NAME,
                    new FunctionWithoutResult<String>() {
                        @Override
                        protected void applyWithoutResult(String input) {
                            doPopulateDimensions();
                        }
                    });

            return result.isExecuted();
        } catch (InterruptedException e) {
            logger.warn("Interrupted while aggregating", e);
            Thread.currentThread().interrupt();
            return false;
        }
    }

    @Override
    public boolean aggregateRawEvents() {
        TryLockFunctionResult<Boolean> result = null;
        do {
            if (result != null) {
                logger.debug(
                        "doAggregateRawEvents signaled that not all events were aggregated in a single transaction, running again.");
            }

            result = aggrEventsTransactionOperations
                    .execute(new TransactionCallback<TryLockFunctionResult<Boolean>>() {
                        @Override
                        public TryLockFunctionResult<Boolean> doInTransaction(TransactionStatus status) {
                            try {
                                return clusterLockService.doInTryLock(AGGREGATION_LOCK_NAME,
                                        new Function<String, Boolean>() {
                                            @Override
                                            public Boolean apply(String input) {
                                                return doAggregateRawEvents();
                                            }
                                        });
                            } catch (InterruptedException e) {
                                logger.warn("Interrupted while aggregating", e);
                                Thread.currentThread().interrupt();
                                return null;
                            }
                        }
                    });

            //Loop if doAggregateRawEvents returns false, this means that there is more to aggregate 
        } while (result != null && result.isExecuted() && !result.getResult());

        return result != null && result.isExecuted();
    }

    @Override
    @Transactional(value = "aggrEventsTransactionManager")
    public boolean purgeRawEvents() {
        try {
            final TryLockFunctionResult<Object> result = this.clusterLockService
                    .doInTryLock(PURGE_RAW_EVENTS_LOCK_NAME, new FunctionWithoutResult<String>() {
                        @Override
                        protected void applyWithoutResult(String input) {
                            doPurgeRawEvents();
                        }
                    });

            return result.isExecuted();
        } catch (InterruptedException e) {
            logger.warn("Interrupted while purging", e);
            Thread.currentThread().interrupt();
            return false;
        }
    }

    @Override
    @Transactional(value = "aggrEventsTransactionManager")
    public boolean purgeEventSessions() {
        try {
            final TryLockFunctionResult<Object> result = this.clusterLockService
                    .doInTryLock(PURGE_EVENT_SESSION_LOCK_NAME, new FunctionWithoutResult<String>() {
                        @Override
                        protected void applyWithoutResult(String input) {
                            eventSessionDao.purgeExpiredEventSessions();
                        }
                    });

            return result.isExecuted();
        } catch (InterruptedException e) {
            logger.warn("Interrupted while purging", e);
            Thread.currentThread().interrupt();
            return false;
        }
    }

    //use local flag to run on first call to doAggregation
    void doPopulateDimensions() {
        doPopulateTimeDimensions();
        doPopulateDateDimensions();
    }

    /**
     * Populate the time dimensions 
     */
    void doPopulateTimeDimensions() {
        final List<TimeDimension> timeDimensions = this.timeDimensionDao.getTimeDimensions();
        if (timeDimensions.isEmpty()) {
            logger.info("No TimeDimensions exist, creating them");
        } else if (timeDimensions.size() != (24 * 60)) {
            this.logger.info(
                    "There are only " + timeDimensions.size() + " time dimensions in the database, there should be "
                            + (24 * 60) + " creating missing dimensions");
        } else {
            this.logger.debug("Found expected " + timeDimensions.size() + " time dimensions");
            return;
        }

        LocalTime nextTime = new LocalTime(0, 0);
        final LocalTime lastTime = new LocalTime(23, 59);

        for (final TimeDimension timeDimension : timeDimensions) {
            LocalTime dimensionTime = timeDimension.getTime();
            if (nextTime.isBefore(dimensionTime)) {
                do {
                    this.timeDimensionDao.createTimeDimension(nextTime);
                    nextTime = nextTime.plusMinutes(1);
                } while (nextTime.isBefore(dimensionTime));
            } else if (nextTime.isAfter(dimensionTime)) {
                do {
                    this.timeDimensionDao.createTimeDimension(dimensionTime);
                    dimensionTime = dimensionTime.plusMinutes(1);
                } while (nextTime.isAfter(dimensionTime));
            }

            nextTime = dimensionTime.plusMinutes(1);
        }

        //Add any missing times from the tail
        while (nextTime.isBefore(lastTime) || nextTime.equals(lastTime)) {
            this.timeDimensionDao.createTimeDimension(nextTime);
            if (nextTime.equals(lastTime)) {
                break;
            }
            nextTime = nextTime.plusMinutes(1);
        }
    }

    void doPopulateDateDimensions() {
        final DateTime now = getNow();

        final AggregationIntervalInfo startIntervalInfo;
        final DateTime oldestPortalEventTimestamp = this.portalEventDao.getOldestPortalEventTimestamp();
        if (oldestPortalEventTimestamp == null || now.isBefore(oldestPortalEventTimestamp)) {
            startIntervalInfo = this.intervalHelper.getIntervalInfo(AggregationInterval.YEAR,
                    now.minus(this.dimensionBuffer));
        } else {
            startIntervalInfo = this.intervalHelper.getIntervalInfo(AggregationInterval.YEAR,
                    oldestPortalEventTimestamp.minus(this.dimensionBuffer));
        }

        final AggregationIntervalInfo endIntervalInfo;
        final DateTime newestPortalEventTimestamp = this.portalEventDao.getNewestPortalEventTimestamp();
        if (newestPortalEventTimestamp == null || now.isAfter(newestPortalEventTimestamp)) {
            endIntervalInfo = this.intervalHelper.getIntervalInfo(AggregationInterval.YEAR,
                    now.plus(this.dimensionBuffer));
        } else {
            endIntervalInfo = this.intervalHelper.getIntervalInfo(AggregationInterval.YEAR,
                    newestPortalEventTimestamp.plus(this.dimensionBuffer));
        }

        final DateMidnight start = startIntervalInfo.getStart().toDateMidnight();
        final DateMidnight end = endIntervalInfo.getEnd().toDateMidnight();

        doPopulateDateDimensions(start, end);
    }

    /**
     * Exists to make this class testable
     */
    DateTime getNow() {
        return DateTime.now();
    }

    void doPopulateDateDimensions(final DateMidnight start, final DateMidnight end) {
        logger.info("Populating DateDimensions between {} and {}", start, end);

        final List<QuarterDetail> quartersDetails = this.eventAggregationManagementDao.getQuartersDetails();
        final List<AcademicTermDetail> academicTermDetails = this.eventAggregationManagementDao
                .getAcademicTermDetails();

        final List<DateDimension> dateDimensions = this.dateDimensionDao.getDateDimensionsBetween(start, end);

        DateMidnight nextDate = start;
        for (final DateDimension dateDimension : dateDimensions) {
            DateMidnight dimensionDate = dateDimension.getDate();
            if (nextDate.isBefore(dimensionDate)) {
                do {
                    createDateDimension(quartersDetails, academicTermDetails, nextDate);
                    nextDate = nextDate.plusDays(1);
                } while (nextDate.isBefore(dimensionDate));
            } else if (nextDate.isAfter(dimensionDate)) {
                do {
                    createDateDimension(quartersDetails, academicTermDetails, dimensionDate);
                    dimensionDate = dimensionDate.plusDays(1);
                } while (nextDate.isAfter(dimensionDate));
            }

            nextDate = dimensionDate.plusDays(1);
        }

        //Add any missing dates from the tail
        while (nextDate.isBefore(end)) {
            createDateDimension(quartersDetails, academicTermDetails, nextDate);
            nextDate = nextDate.plusDays(1);
        }
    }

    /**
     * Creates a date dimension, handling the quarter and term lookup logic
     */
    protected void createDateDimension(List<QuarterDetail> quartersDetails,
            List<AcademicTermDetail> academicTermDetails, DateMidnight date) {

        final QuarterDetail quarterDetail = EventDateTimeUtils.findDateRangeSorted(date, quartersDetails);
        final AcademicTermDetail termDetail = EventDateTimeUtils.findDateRangeSorted(date, academicTermDetails);
        this.dateDimensionDao.createDateDimension(date, quarterDetail.getQuarterId(),
                termDetail != null ? termDetail.getTermName() : null);
    }

    /**
     * @return true if all events for the time period were aggregated, false if not
     */
    boolean doAggregateRawEvents() {
        if (!this.checkedDimensions.get() && this.checkedDimensions.compareAndSet(false, true)) {
            //First time aggregation has happened, run populateDimensions to ensure enough dimension data exists
            final boolean populatedDimensions = this.populateDimensions();
            if (!populatedDimensions) {
                this.logger.warn(
                        "First time doAggregateRawEvents has run and populateDimensions returned false, assuming current dimension data is available");
            }
        }

        final IEventAggregatorStatus eventAggregatorStatus = eventAggregationManagementDao
                .getEventAggregatorStatus(ProcessingType.AGGREGATION, true);

        //Update status with current server name
        final String serverName = this.portalInfoProvider.getServerName();
        eventAggregatorStatus.setServerName(serverName);

        //Calculate date range for aggregation
        DateTime lastAggregated = eventAggregatorStatus.getLastEventDate();
        if (lastAggregated == null) {
            lastAggregated = new DateTime(0);
        }

        DateTime newestEventTime = DateTime.now().minus(this.aggregationDelay).secondOfMinute().roundFloorCopy();

        logger.debug("Starting aggregation of events between {} (inc) and {} (exc)", lastAggregated,
                newestEventTime);
        final MutableInt events = new MutableInt();

        //Do aggregation, capturing the start and end dates
        eventAggregatorStatus.setLastStart(DateTime.now());
        final long start = System.nanoTime();
        portalEventDao.aggregatePortalEvents(lastAggregated, newestEventTime, this.eventAggregationBatchSize,
                new AggregateEventsHandler(events, eventAggregatorStatus));
        eventAggregatorStatus.setLastEnd(new DateTime());

        logger.debug("Aggregated {} events between {} and {} in {}ms", new Object[] { events, lastAggregated,
                newestEventTime, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) });

        //Store the results of the aggregation
        eventAggregationManagementDao.updateEventAggregatorStatus(eventAggregatorStatus);

        return this.eventAggregationBatchSize <= 0 || events.intValue() < this.eventAggregationBatchSize;
    }

    void doPurgeRawEvents() {
        final IEventAggregatorStatus eventPurgerStatus = eventAggregationManagementDao
                .getEventAggregatorStatus(ProcessingType.PURGING, true);

        //Update status with current server name
        final String serverName = this.portalInfoProvider.getServerName();
        eventPurgerStatus.setServerName(serverName);
        eventPurgerStatus.setLastStart(new DateTime());

        //Determine date of most recently aggregated data
        final IEventAggregatorStatus eventAggregatorStatus = eventAggregationManagementDao
                .getEventAggregatorStatus(ProcessingType.AGGREGATION, false);
        if (eventAggregatorStatus == null || eventAggregatorStatus.getLastEventDate() == null) {
            //Nothing has been aggregated, skip purging

            eventPurgerStatus.setLastEnd(new DateTime());
            eventAggregationManagementDao.updateEventAggregatorStatus(eventPurgerStatus);

            return;
        }

        //Calculate purge end date from most recent aggregation minus the purge delay
        final DateTime lastAggregated = eventAggregatorStatus.getLastEventDate();
        final DateTime purgeEnd = lastAggregated.minus(this.purgeDelay);

        //Purge events
        logger.debug("Starting purge of events before {}", purgeEnd);
        final long start = System.nanoTime();
        final int events = portalEventDao.deletePortalEventsBefore(purgeEnd);
        logger.debug("Purged {} events before {} in {}ms",
                new Object[] { events, purgeEnd, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) });

        //Update the status object and store it
        eventPurgerStatus.setLastEventDate(purgeEnd.minusMillis(1)); //decrement by 1ms since deletePortalEventsBefore uses lessThan and not lessThanEqualTo 
        eventPurgerStatus.setLastEnd(new DateTime());
        eventAggregationManagementDao.updateEventAggregatorStatus(eventPurgerStatus);
    }

    private final class AggregateEventsHandler extends FunctionWithoutResult<PortalEvent> {
        private final MutableInt eventCounter;
        private final IEventAggregatorStatus eventAggregatorStatus;

        private final Map<AggregationInterval, AggregationIntervalInfo> currentIntervalInfo = new EnumMap<AggregationInterval, AggregationIntervalInfo>(
                AggregationInterval.class);
        private final Map<AggregationInterval, AggregationIntervalInfo> readOnlyIntervalInfo = Collections
                .unmodifiableMap(currentIntervalInfo);

        //Local caches of aggregator config data, shouldn't ever change for the duration of an aggregation run
        private final Map<Class<? extends IPortalEventAggregator>, AggregatedGroupConfig> aggregatorGroupConfigs = new HashMap<Class<? extends IPortalEventAggregator>, AggregatedGroupConfig>();
        private final Map<Class<? extends IPortalEventAggregator>, AggregatedIntervalConfig> aggregatorIntervalConfigs = new HashMap<Class<? extends IPortalEventAggregator>, AggregatedIntervalConfig>();
        private final Map<Class<? extends IPortalEventAggregator>, Map<AggregationInterval, AggregationIntervalInfo>> aggregatorReadOnlyIntervalInfo = new HashMap<Class<? extends IPortalEventAggregator>, Map<AggregationInterval, AggregationIntervalInfo>>();
        private final AggregatedGroupConfig defaultAggregatedGroupConfig;
        private final AggregatedIntervalConfig defaultAggregatedIntervalConfig;

        private AggregateEventsHandler(MutableInt eventCounter, IEventAggregatorStatus eventAggregatorStatus) {
            this.eventCounter = eventCounter;
            this.eventAggregatorStatus = eventAggregatorStatus;
            this.defaultAggregatedGroupConfig = eventAggregationManagementDao.getDefaultAggregatedGroupConfig();
            this.defaultAggregatedIntervalConfig = eventAggregationManagementDao
                    .getDefaultAggregatedIntervalConfig();
        }

        @Override
        protected void applyWithoutResult(PortalEvent event) {
            final DateTime eventDate = event.getTimestampAsDate();

            //If no interval data yet populate it.
            if (this.currentIntervalInfo.isEmpty()) {
                final DateTime intervalDate;
                final DateTime lastEventDate = eventAggregatorStatus.getLastEventDate();
                if (lastEventDate != null) {
                    //If there was a previously aggregated event use that date to make sure an interval is not missed
                    intervalDate = lastEventDate;
                } else {
                    //Otherwise just use the current event date
                    intervalDate = eventDate;
                }

                for (final AggregationInterval interval : AggregationInterval.values()) {
                    final AggregationIntervalInfo intervalInfo = intervalHelper.getIntervalInfo(interval,
                            intervalDate);
                    if (intervalInfo != null) {
                        this.currentIntervalInfo.put(interval, intervalInfo);
                    } else {
                        this.currentIntervalInfo.remove(interval);
                    }
                }
            }

            //Check each interval to see if an interval boundary has been crossed
            for (final AggregationInterval interval : AggregationInterval.values()) {
                AggregationIntervalInfo intervalInfo = this.currentIntervalInfo.get(interval);
                if (intervalInfo != null && !intervalInfo.getEnd().isAfter(eventDate)) { //if there is no IntervalInfo that interval must not be supported in the current environment 
                    logger.debug("Crossing {} Interval, triggerd by {}", interval, event);
                    this.doHandleIntervalBoundary(interval, this.currentIntervalInfo);

                    intervalInfo = intervalHelper.getIntervalInfo(interval, eventDate);
                    this.currentIntervalInfo.put(interval, intervalInfo);
                }
            }

            //Aggregate the event
            this.doAggregateEvent(event);

            //Update the status object with the event date
            eventAggregatorStatus.setLastEventDate(eventDate);
        }

        private void doAggregateEvent(PortalEvent item) {
            eventCounter.increment();

            //Load or create the event session
            EventSession eventSession;
            if (item instanceof LoginEvent) {
                eventSession = eventSessionDao.createEventSession((LoginEvent) item);
            } else {
                eventSession = eventSessionDao.getEventSession(item.getEventSessionId());
            }

            //Give each aggregator a chance at the event
            for (final IPortalEventAggregator<PortalEvent> portalEventAggregator : portalEventAggregators) {
                if (portalEventAggregator.supports(item.getClass())) {
                    final Class<? extends IPortalEventAggregator> aggregatorType = portalEventAggregator.getClass();

                    //Get aggregator specific interval info map
                    final Map<AggregationInterval, AggregationIntervalInfo> aggregatorIntervalInfo = this
                            .getAggregatorIntervalInfo(aggregatorType);

                    //If there is an event session get the aggregator specific version of it
                    if (eventSession != null) {
                        final AggregatedGroupConfig aggregatorGroupConfig = getAggregatorGroupConfig(
                                aggregatorType);
                        eventSession = eventSession.getFilteredEventSession(aggregatorGroupConfig);
                    }

                    //Aggregation magic happens here!
                    portalEventAggregator.aggregateEvent(item, eventSession, aggregatorIntervalInfo);
                }
            }
        }

        private void doHandleIntervalBoundary(AggregationInterval interval,
                Map<AggregationInterval, AggregationIntervalInfo> intervals) {
            for (final IPortalEventAggregator<PortalEvent> portalEventAggregator : portalEventAggregators) {

                final Class<? extends IPortalEventAggregator> aggregatorType = portalEventAggregator.getClass();
                final AggregatedIntervalConfig aggregatorIntervalConfig = this
                        .getAggregatorIntervalConfig(aggregatorType);

                //If the aggreagator is configured to use the interval notify it of the interval boundary
                if (aggregatorIntervalConfig.isIncluded(interval)) {
                    final Map<AggregationInterval, AggregationIntervalInfo> aggregatorIntervalInfo = this
                            .getAggregatorIntervalInfo(aggregatorType);
                    portalEventAggregator.handleIntervalBoundary(interval, aggregatorIntervalInfo);
                }
            }
        }

        /**
         * @return The interval info map for the aggregator
         */
        protected Map<AggregationInterval, AggregationIntervalInfo> getAggregatorIntervalInfo(
                final Class<? extends IPortalEventAggregator> aggregatorType) {
            final AggregatedIntervalConfig aggregatorIntervalConfig = this
                    .getAggregatorIntervalConfig(aggregatorType);

            Map<AggregationInterval, AggregationIntervalInfo> intervalInfo = this.aggregatorReadOnlyIntervalInfo
                    .get(aggregatorType);
            if (intervalInfo == null) {
                intervalInfo = Maps.filterKeys(this.readOnlyIntervalInfo, new Predicate<AggregationInterval>() {
                    @Override
                    public boolean apply(AggregationInterval input) {
                        return aggregatorIntervalConfig.isIncluded(input);
                    }
                });
            }

            return intervalInfo;
        }

        /**
         * @return The group config for the aggregator, returns the default config if no aggregator specific config is set
         */
        protected AggregatedGroupConfig getAggregatorGroupConfig(
                final Class<? extends IPortalEventAggregator> aggregatorType) {
            AggregatedGroupConfig config = this.aggregatorGroupConfigs.get(aggregatorType);
            if (config == null) {
                config = eventAggregationManagementDao.getAggregatedGroupConfig(aggregatorType);
                if (config == null) {
                    config = this.defaultAggregatedGroupConfig;
                }
                this.aggregatorGroupConfigs.put(aggregatorType, config);
            }
            return config;
        }

        /**
         * @return The interval config for the aggregator, returns the default config if no aggregator specific config is set
         */
        protected AggregatedIntervalConfig getAggregatorIntervalConfig(
                final Class<? extends IPortalEventAggregator> aggregatorType) {
            AggregatedIntervalConfig config = this.aggregatorIntervalConfigs.get(aggregatorType);
            if (config == null) {
                config = eventAggregationManagementDao.getAggregatedIntervalConfig(aggregatorType);
                if (config == null) {
                    config = this.defaultAggregatedIntervalConfig;
                }
                this.aggregatorIntervalConfigs.put(aggregatorType, config);
            }
            return config;
        }
    }
}