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

Java tutorial

Introduction

Here is the source code for org.jasig.portal.events.aggr.JpaBaseAggregationDao.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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang.ArrayUtils;
import org.jasig.portal.events.aggr.dao.jpa.DateDimensionImpl;
import org.jasig.portal.events.aggr.dao.jpa.DateDimensionImpl_;
import org.jasig.portal.events.aggr.dao.jpa.TimeDimensionImpl;
import org.jasig.portal.events.aggr.dao.jpa.TimeDimensionImpl_;
import org.jasig.portal.events.aggr.groups.AggregatedGroupMapping;
import org.jasig.portal.events.aggr.groups.AggregatedGroupMappingImpl;
import org.jasig.portal.jpa.BaseAggrEventsJpaDao;
import org.jasig.portal.jpa.OpenEntityManager;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.springframework.beans.factory.annotation.Autowired;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;

/**
 * Base for JPA DAOs that handle {@link BaseAggregationImpl} subclasses. Provides impls of the standard methods defined by 
 * {@link BaseAggregationPrivateDao}. Note that subclasses MUST call {@link #afterPropertiesSet()} if they override it or this
 * class WILL NOT WORK
 * 
 * @author Eric Dalquist
 * @param <T> The entity type being aggregated
 * @param <K> The entity primary key
 */
public abstract class JpaBaseAggregationDao<T extends BaseAggregationImpl<K>, K extends BaseAggregationKey>
        extends BaseAggrEventsJpaDao implements BaseAggregationPrivateDao<T, K> {

    private final Class<T> aggregationEntityType;
    private HibernateCacheEvictor hibernateCacheEvictor;

    protected CriteriaQuery<T> findAggregationByDateTimeIntervalQuery;
    protected CriteriaQuery<T> findAggregationByDateTimeIntervalGroupQuery;
    protected CriteriaQuery<T> findAggregationsByDateRangeQuery;
    protected CriteriaQuery<T> findUnclosedAggregationsByDateRangeQuery;
    protected ParameterExpression<TimeDimension> timeDimensionParameter;
    protected ParameterExpression<DateDimension> dateDimensionParameter;
    protected ParameterExpression<AggregationInterval> intervalParameter;
    protected ParameterExpression<AggregatedGroupMapping> aggregatedGroupParameter;
    protected ParameterExpression<Set> aggregatedGroupsParameter;
    protected ParameterExpression<LocalDate> startDate;
    protected ParameterExpression<LocalDate> endMinusOneDate;
    protected ParameterExpression<LocalDate> endDate;
    protected ParameterExpression<LocalTime> startTime;
    protected ParameterExpression<LocalTime> endTime;

    public JpaBaseAggregationDao(Class<T> aggregationEntityType) {
        this.aggregationEntityType = aggregationEntityType;
    }

    @Autowired
    public void setHibernateCacheEvictor(HibernateCacheEvictor hibernateCacheEvictor) {
        this.hibernateCacheEvictor = hibernateCacheEvictor;
    }

    /**
     * Add any fetches needed for the following queries:
     *  findAggregationByDateTimeIntervalQuery
     *  findUnclosedAggregationsByDateRangeQuery
     */
    protected abstract void addFetches(Root<T> root);

    /**
     * Add the additional predicate needed to find the unclosed aggregates 
     */
    protected abstract void addUnclosedPredicate(CriteriaBuilder cb, Root<T> root, List<Predicate> keyPredicates);

    /**
     * Add the additional predicate needed if using an extension of {@link BaseAggregationKey}
     */
    protected void addAggregationSpecificKeyPredicate(CriteriaBuilder cb, Root<T> root,
            List<Predicate> keyPredicates) {
    }

    /**
     * Bind the non-standard key parameters from the extension of {@link BaseAggregationKey} for standard queries
     */
    protected void bindAggregationSpecificKeyParameters(TypedQuery<T> query, K key) {
    }

    /**
     * Bind the non-standard key parameters from the extension of {@link BaseAggregationKey} for natual id queries
     */
    protected void bindAggregationSpecificKeyParameters(NaturalIdQuery<T> query, K key) {
    }

    /**
     * For subclasses to use to create additional {@link ParameterExpression}s
     */
    protected void createParameterExpressions() {
    }

    /**
     * For subclasses to use to create additional {@link CriteriaQuery}s
     */
    protected void createCriteriaQueries() {
    }

    /**
     * Create a new aggregation instance
     */
    protected abstract T createAggregationInstance(K key);

    /**
     * Get the aggregation key for this instance
     */
    protected abstract K getAggregationKey(T instance);

    @Override
    public final void afterPropertiesSet() throws Exception {
        this.timeDimensionParameter = this.createParameterExpression(TimeDimension.class, "timeDimension");
        this.dateDimensionParameter = this.createParameterExpression(DateDimension.class, "dateDimension");
        this.intervalParameter = this.createParameterExpression(AggregationInterval.class, "interval");
        this.aggregatedGroupParameter = this.createParameterExpression(AggregatedGroupMapping.class,
                "aggregatedGroup");
        this.aggregatedGroupsParameter = this.createParameterExpression(Set.class, "aggregatedGroups");
        this.startDate = this.createParameterExpression(LocalDate.class, "startDate");
        this.endMinusOneDate = this.createParameterExpression(LocalDate.class, "endMinusOneDate");
        this.endDate = this.createParameterExpression(LocalDate.class, "endDate");
        this.startTime = this.createParameterExpression(LocalTime.class, "startTime");
        this.endTime = this.createParameterExpression(LocalTime.class, "endTime");
        this.createParameterExpressions();

        this.findAggregationByDateTimeIntervalQuery = this
                .createCriteriaQuery(new Function<CriteriaBuilder, CriteriaQuery<T>>() {
                    @Override
                    public CriteriaQuery<T> apply(CriteriaBuilder cb) {
                        final CriteriaQuery<T> criteriaQuery = cb.createQuery(aggregationEntityType);

                        final Root<T> ba = criteriaQuery.from(aggregationEntityType);

                        addFetches(ba);

                        criteriaQuery.select(ba);
                        criteriaQuery.where(
                                cb.equal(ba.get(BaseAggregationImpl_.dateDimension), dateDimensionParameter),
                                cb.equal(ba.get(BaseAggregationImpl_.timeDimension), timeDimensionParameter),
                                cb.equal(ba.get(BaseAggregationImpl_.interval), intervalParameter));

                        return criteriaQuery;
                    }
                });

        this.findAggregationByDateTimeIntervalGroupQuery = this
                .createCriteriaQuery(new Function<CriteriaBuilder, CriteriaQuery<T>>() {
                    @Override
                    public CriteriaQuery<T> apply(CriteriaBuilder cb) {
                        final CriteriaQuery<T> criteriaQuery = cb.createQuery(aggregationEntityType);
                        final Root<T> ba = criteriaQuery.from(aggregationEntityType);

                        final List<Predicate> keyPredicates = new ArrayList<Predicate>();
                        keyPredicates
                                .add(cb.equal(ba.get(BaseAggregationImpl_.dateDimension), dateDimensionParameter));
                        keyPredicates
                                .add(cb.equal(ba.get(BaseAggregationImpl_.timeDimension), timeDimensionParameter));
                        keyPredicates.add(cb.equal(ba.get(BaseAggregationImpl_.interval), intervalParameter));
                        keyPredicates.add(
                                cb.equal(ba.get(BaseAggregationImpl_.aggregatedGroup), aggregatedGroupParameter));
                        addAggregationSpecificKeyPredicate(cb, ba, keyPredicates);

                        criteriaQuery.select(ba);
                        criteriaQuery.where(keyPredicates.toArray(new Predicate[keyPredicates.size()]));

                        return criteriaQuery;
                    }
                });

        this.findAggregationsByDateRangeQuery = this
                .createCriteriaQuery(new Function<CriteriaBuilder, CriteriaQuery<T>>() {
                    @Override
                    public CriteriaQuery<T> apply(CriteriaBuilder cb) {
                        final CriteriaQuery<T> criteriaQuery = cb.createQuery(aggregationEntityType);

                        final Root<T> ba = criteriaQuery.from(aggregationEntityType);
                        final Join<T, DateDimensionImpl> dd = ba.join(BaseAggregationImpl_.dateDimension,
                                JoinType.LEFT);
                        final Join<T, TimeDimensionImpl> td = ba.join(BaseAggregationImpl_.timeDimension,
                                JoinType.LEFT);

                        final List<Predicate> keyPredicates = new ArrayList<Predicate>();
                        keyPredicates.add(cb.and( //Restrict results by outer date range
                                cb.greaterThanOrEqualTo(dd.get(DateDimensionImpl_.date), startDate),
                                cb.lessThan(dd.get(DateDimensionImpl_.date), endDate)));
                        keyPredicates.add(cb.or( //Restrict start of range by time as well
                                cb.greaterThan(dd.get(DateDimensionImpl_.date), startDate),
                                cb.greaterThanOrEqualTo(td.get(TimeDimensionImpl_.time), startTime)));
                        keyPredicates.add(cb.or( //Restrict end of range by time as well
                                cb.lessThan(dd.get(DateDimensionImpl_.date), endMinusOneDate),
                                cb.lessThan(td.get(TimeDimensionImpl_.time), endTime)));
                        keyPredicates.add(cb.equal(ba.get(BaseAggregationImpl_.interval), intervalParameter));
                        keyPredicates
                                .add(ba.get(BaseAggregationImpl_.aggregatedGroup).in(aggregatedGroupsParameter));
                        addAggregationSpecificKeyPredicate(cb, ba, keyPredicates);

                        criteriaQuery.select(ba);
                        criteriaQuery.where(keyPredicates.toArray(new Predicate[keyPredicates.size()]));
                        criteriaQuery.orderBy(cb.desc(dd.get(DateDimensionImpl_.date)),
                                cb.desc(td.get(TimeDimensionImpl_.time)));

                        return criteriaQuery;
                    }
                });

        /*
         * Similar to the previous query but only returns aggregates that also match the unclosed predicate generated
         * by the subclass. This is used for finding aggregates that missed having intervalComplete called due to
         * interval boundary placement.
         */
        this.findUnclosedAggregationsByDateRangeQuery = this
                .createCriteriaQuery(new Function<CriteriaBuilder, CriteriaQuery<T>>() {
                    @Override
                    public CriteriaQuery<T> apply(CriteriaBuilder cb) {
                        final CriteriaQuery<T> criteriaQuery = cb.createQuery(aggregationEntityType);

                        final Root<T> ba = criteriaQuery.from(aggregationEntityType);
                        final Join<T, DateDimensionImpl> dd = ba.join(BaseAggregationImpl_.dateDimension,
                                JoinType.LEFT);
                        final Join<T, TimeDimensionImpl> td = ba.join(BaseAggregationImpl_.timeDimension,
                                JoinType.LEFT);

                        addFetches(ba);

                        final List<Predicate> keyPredicates = new ArrayList<Predicate>();
                        keyPredicates.add(cb.and( //Restrict results by outer date range
                                cb.greaterThanOrEqualTo(dd.get(DateDimensionImpl_.date), startDate),
                                cb.lessThan(dd.get(DateDimensionImpl_.date), endDate)));
                        keyPredicates.add(cb.or( //Restrict start of range by time as well
                                cb.greaterThan(dd.get(DateDimensionImpl_.date), startDate),
                                cb.greaterThanOrEqualTo(td.get(TimeDimensionImpl_.time), startTime)));
                        keyPredicates.add(cb.or( //Restrict end of range by time as well
                                cb.lessThan(dd.get(DateDimensionImpl_.date), endMinusOneDate),
                                cb.lessThan(td.get(TimeDimensionImpl_.time), endTime)));
                        keyPredicates.add(cb.equal(ba.get(BaseAggregationImpl_.interval), intervalParameter));
                        //No aggregation specific key bits here, we only have start/end/interval parameters to work with
                        addUnclosedPredicate(cb, ba, keyPredicates);

                        criteriaQuery.select(ba);
                        criteriaQuery.where(keyPredicates.toArray(new Predicate[keyPredicates.size()]));

                        return criteriaQuery;
                    }
                });

        this.createCriteriaQueries();
    }

    @Override
    public final List<T> getAggregations(DateTime start, DateTime end, K key,
            AggregatedGroupMapping... aggregatedGroupMappings) {
        if (!start.isBefore(end)) {
            throw new IllegalArgumentException("Start must be before End: " + start + " - " + end);
        }
        final LocalDate startDate = start.toLocalDate();
        LocalDate endDate = end.toLocalDate();
        if (startDate.equals(endDate)) {
            endDate = endDate.plusDays(1);
        }

        final TypedQuery<T> query = this.createCachedQuery(findAggregationsByDateRangeQuery);

        query.setParameter(this.startDate, start.toLocalDate());
        query.setParameter(this.startTime, start.toLocalTime());

        query.setParameter(this.endDate, end.toLocalDate());
        query.setParameter(this.endTime, end.toLocalTime());
        query.setParameter(this.endMinusOneDate, end.minusDays(1).toLocalDate());

        query.setParameter(this.intervalParameter, key.getInterval());

        this.bindAggregationSpecificKeyParameters(query, key);

        aggregatedGroupMappings = (AggregatedGroupMapping[]) ArrayUtils.add(aggregatedGroupMappings,
                key.getAggregatedGroup());
        query.setParameter(this.aggregatedGroupsParameter, ImmutableSet.copyOf(aggregatedGroupMappings));

        return query.getResultList();
    }

    @Override
    public final Map<K, T> getAggregationsForInterval(DateDimension dateDimension, TimeDimension timeDimension,
            AggregationInterval interval) {
        final TypedQuery<T> query = this.createQuery(this.findAggregationByDateTimeIntervalQuery);
        query.setParameter(this.dateDimensionParameter, dateDimension);
        query.setParameter(this.timeDimensionParameter, timeDimension);
        query.setParameter(this.intervalParameter, interval);

        final List<T> results = query.getResultList();
        final Map<K, T> resultMap = new HashMap<K, T>();
        for (final T result : results) {
            final K key = this.getAggregationKey(result);
            resultMap.put(key, result);
        }

        return resultMap;
    }

    @Override
    @OpenEntityManager(unitName = PERSISTENCE_UNIT_NAME)
    public final T getAggregation(K key) {
        final NaturalIdQuery<T> query = this.createNaturalIdQuery(this.aggregationEntityType);
        query.using(BaseAggregationImpl_.dateDimension, (DateDimensionImpl) key.getDateDimension());
        query.using(BaseAggregationImpl_.timeDimension, (TimeDimensionImpl) key.getTimeDimension());
        query.using(BaseAggregationImpl_.interval, key.getInterval());
        query.using(BaseAggregationImpl_.aggregatedGroup, (AggregatedGroupMappingImpl) key.getAggregatedGroup());

        this.bindAggregationSpecificKeyParameters(query, key);

        return query.load();
    }

    @Override
    public Collection<T> getUnclosedAggregations(DateTime start, DateTime end, AggregationInterval interval) {
        if (!start.isBefore(end)) {
            throw new IllegalArgumentException("Start must be before End: " + start + " - " + end);
        }
        final LocalDate startDate = start.toLocalDate();
        LocalDate endDate = end.toLocalDate();
        if (startDate.equals(endDate)) {
            endDate = endDate.plusDays(1);
        }

        final TypedQuery<T> query = this.createQuery(findUnclosedAggregationsByDateRangeQuery);

        query.setParameter(this.startDate, startDate);
        query.setParameter(this.startTime, start.toLocalTime());

        query.setParameter(this.endDate, endDate);
        query.setParameter(this.endTime, end.toLocalTime());
        query.setParameter(this.endMinusOneDate, end.minusDays(1).toLocalDate());

        query.setParameter(this.intervalParameter, interval);

        //Need set to handle duplicate results from join
        return new LinkedHashSet<T>(query.getResultList());
    }

    @AggrEventsTransactional
    @Override
    public final T createAggregation(K key) {
        final T aggregation = createAggregationInstance(key);

        this.getEntityManager().persist(aggregation);

        return aggregation;
    }

    @AggrEventsTransactional
    @Override
    public final void updateAggregation(T aggregation) {
        this.getEntityManager().persist(aggregation);
    }

    @AggrEventsTransactional
    @Override
    public final void updateAggregations(Iterable<T> aggregations, boolean removeFromCache) {
        final EntityManager entityManager = this.getEntityManager();

        for (final T aggregation : aggregations) {
            entityManager.persist(aggregation);

            if (removeFromCache) {
                this.hibernateCacheEvictor.evictEntity(aggregation.getClass(), aggregation.getId());
            }
        }
    }
}