org.apereo.portal.events.aggr.PortalEventDimensionPopulatorImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apereo.portal.events.aggr.PortalEventDimensionPopulatorImpl.java

Source

/**
 * 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.List;
import org.apereo.portal.concurrency.locking.IClusterLockService;
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.handlers.db.IPortalEventDao;
import org.apereo.portal.jpa.BaseAggrEventsJpaDao;
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.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class PortalEventDimensionPopulatorImpl extends BaseAggrEventsJpaDao
        implements DisposableBean, PortalEventDimensionPopulator {
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private TimeDimensionDao timeDimensionDao;
    private DateDimensionDao dateDimensionDao;
    private AggregationIntervalHelper intervalHelper;
    private IPortalEventDao portalEventDao;
    private IEventAggregationManagementDao eventAggregationManagementDao;
    private IClusterLockService clusterLockService;

    private ReadablePeriod dimensionBuffer = Period.days(30);

    private volatile boolean checkedDimensions = false;
    private volatile boolean shutdown = false;

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

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

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

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

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

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

    @Value("${org.apereo.portal.events.aggr.PortalEventDimensionPopulatorImpl.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
    public void destroy() throws Exception {
        this.shutdown = true;
    }

    @Override
    public boolean isCheckedDimensions() {
        return checkedDimensions;
    }

    @Override
    @AggrEventsTransactional
    public void doPopulateDimensions() {
        if (shutdown) {
            logger.warn("populateDimensions called after shutdown, ignoring call");
        }

        if (!this.clusterLockService.isLockOwner(DIMENSION_LOCK_NAME)) {
            throw new IllegalStateException(
                    "The cluster lock " + DIMENSION_LOCK_NAME + " must be owned by the current thread and server");
        }

        doPopulateTimeDimensions();
        doPopulateDateDimensions();
        doUpdateDateDimensions();

        //Immediately flush all date/time dimension changes to the database
        this.getEntityManager().flush();

        this.checkedDimensions = true;
    }

    private void checkShutdown() {
        if (shutdown) {
            //Mark ourselves as interupted and throw an exception
            Thread.currentThread().interrupt();
            throw new RuntimeException("uPortal is shutting down, throwing an exeption to stop processing");
        }
    }

    /** Populate the time dimensions */
    final 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 {
                    checkShutdown();
                    this.timeDimensionDao.createTimeDimension(nextTime);
                    nextTime = nextTime.plusMinutes(1);
                } while (nextTime.isBefore(dimensionTime));
            } else if (nextTime.isAfter(dimensionTime)) {
                do {
                    checkShutdown();
                    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)) {
            checkShutdown();
            this.timeDimensionDao.createTimeDimension(nextTime);
            if (nextTime.equals(lastTime)) {
                break;
            }
            nextTime = nextTime.plusMinutes(1);
        }
    }

    final 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);
    }

    final 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 {
                    checkShutdown();
                    createDateDimension(quartersDetails, academicTermDetails, nextDate);
                    nextDate = nextDate.plusDays(1);
                } while (nextDate.isBefore(dimensionDate));
            } else if (nextDate.isAfter(dimensionDate)) {
                do {
                    checkShutdown();
                    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)) {
            checkShutdown();
            createDateDimension(quartersDetails, academicTermDetails, nextDate);
            nextDate = nextDate.plusDays(1);
        }
    }

    /** Populate the term/quarter data for dimensions that are missing the data */
    final void doUpdateDateDimensions() {
        final List<DateDimension> dateDimensions = this.dateDimensionDao.getDateDimensionsWithoutTerm();
        final List<AcademicTermDetail> academicTermDetails = this.eventAggregationManagementDao
                .getAcademicTermDetails();

        for (final DateDimension dateDimension : dateDimensions) {
            final DateMidnight date = dateDimension.getDate();
            final AcademicTermDetail termDetail = EventDateTimeUtils.findDateRangeSorted(date, academicTermDetails);
            if (termDetail != null) {
                dateDimension.setTerm(termDetail.getTermName());
                this.dateDimensionDao.updateDateDimension(dateDimension);
            }
        }
    }

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

    /** 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);
    }
}