org.jasig.schedassist.impl.owner.SpringJDBCAvailableScheduleDaoImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.impl.owner.SpringJDBCAvailableScheduleDaoImpl.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.schedassist.impl.owner;

import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.sql.DataSource;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.schedassist.impl.events.AvailableScheduleChangedEvent;
import org.jasig.schedassist.model.AvailableBlock;
import org.jasig.schedassist.model.AvailableBlockBuilder;
import org.jasig.schedassist.model.AvailableSchedule;
import org.jasig.schedassist.model.IScheduleOwner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Spring JDBC backed implementation of {@link AvailableScheduleDao}.
 * 
 * @author Nicholas Blair, nblair@doit.wisc.edu
 * @version $Id: SpringJDBCAvailableScheduleDaoImpl.java 2517 2010-09-09 18:40:54Z npblair $
 */
@Service("availableScheduleDao")
public class SpringJDBCAvailableScheduleDaoImpl implements AvailableScheduleDao {

    private Log LOG = LogFactory.getLog(this.getClass());

    private SimpleJdbcTemplate simpleJdbcTemplate;
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * @param dataSource the dataSource to set
     */
    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
    }

    /**
     * @param applicationEventPublisher the applicationEventPublisher to set
     */
    @Autowired
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#addToSchedule(org.jasig.schedassist.model.IScheduleOwner, org.jasig.schedassist.model.AvailableBlock)
     */
    @Transactional
    @Override
    public AvailableSchedule addToSchedule(final IScheduleOwner owner, final AvailableBlock block) {
        // expand input block and call overloaded version
        Set<AvailableBlock> blockExpanded = AvailableBlockBuilder.expand(block, 1);
        return addToSchedule(owner, blockExpanded);
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#addToSchedule(org.jasig.schedassist.model.IScheduleOwner, java.util.Set)
     */
    @Transactional
    @Override
    public AvailableSchedule addToSchedule(final IScheduleOwner owner, final Set<AvailableBlock> blocks) {
        // retrieve existing schedule
        AvailableSchedule stored = retrieve(owner);

        // expand it to minimum possible size
        SortedSet<AvailableBlock> storedExpanded = AvailableBlockBuilder.expand(stored.getAvailableBlocks(), 1);
        // expand the argument to minimum possible size blocks
        SortedSet<AvailableBlock> blocksExpanded = AvailableBlockBuilder.expand(blocks, 1);

        // since AvailableBlock equals and hashCode ignore location, call remove first to get
        // rid of any blocks that have matching times
        storedExpanded.removeAll(blocksExpanded);
        // add the new blocks to the expanded set
        boolean modified = storedExpanded.addAll(blocksExpanded);
        if (modified) {
            replaceSchedule(owner, storedExpanded);
        }

        // retrieve the new complete schedule and return
        return retrieve(owner);
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#clearAllBlocks(org.jasig.schedassist.model.IScheduleOwner)
     */
    @Transactional
    @Override
    public void clearAllBlocks(final IScheduleOwner owner) {
        // delete all old blocks
        LOG.warn("issuing clearAllBlocks for owner " + owner);
        int rowsUpdated = this.simpleJdbcTemplate.update("delete from schedules where owner_id = ?", owner.getId());
        LOG.warn("deleted " + rowsUpdated + " for owner " + owner);
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#removeFromSchedule(org.jasig.schedassist.model.IScheduleOwner, org.jasig.schedassist.model.AvailableBlock)
     */
    @Transactional
    @Override
    public AvailableSchedule removeFromSchedule(final IScheduleOwner owner, final AvailableBlock block) {
        // expand input block and call overloaded version
        Set<AvailableBlock> blockExpanded = AvailableBlockBuilder.expand(block, 1);
        return removeFromSchedule(owner, blockExpanded);
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#removeFromSchedule(org.jasig.schedassist.model.IScheduleOwner, java.util.Set)
     */
    @Transactional
    @Override
    public AvailableSchedule removeFromSchedule(final IScheduleOwner owner,
            final Set<AvailableBlock> blocksToRemove) {
        // retrieve existing schedule
        AvailableSchedule stored = retrieve(owner);

        // expand it to minimum possible size
        SortedSet<AvailableBlock> expanded = AvailableBlockBuilder.expand(stored.getAvailableBlocks(), 1);
        // expand the argument to minimum possible size blocks
        SortedSet<AvailableBlock> blocksToRemoveExpanded = AvailableBlockBuilder.expand(blocksToRemove, 1);

        boolean modified = false;
        for (AvailableBlock toRemove : blocksToRemoveExpanded) {
            if (expanded.contains(toRemove)) {
                // remove the specified block
                boolean result = expanded.remove(toRemove);
                if (result && !modified) {
                    modified = true;
                }
            }
        }

        if (modified) {
            replaceSchedule(owner, expanded);
        }
        // retrieve the new complete schedule and return
        return retrieve(owner);
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#retrieve(org.jasig.schedassist.model.IScheduleOwner)
     */
    @Override
    public AvailableSchedule retrieve(final IScheduleOwner owner) {
        Set<AvailableBlock> availableBlocks = internalRetrieveSchedule(owner);
        AvailableSchedule schedule = new AvailableSchedule(availableBlocks);
        return schedule;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#retrieve(org.jasig.schedassist.model.IScheduleOwner, java.util.Date, java.util.Date)
     */
    @Override
    public AvailableSchedule retrieve(IScheduleOwner owner, Date startTime, Date endTime) {
        Set<AvailableBlock> storedBlocks = internalRetrieveSchedule(owner, startTime, endTime);
        AvailableSchedule schedule = new AvailableSchedule(storedBlocks);
        return schedule;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#retrieveWeeklySchedule(org.jasig.schedassist.model.IScheduleOwner, java.util.Date)
     */
    @Override
    public AvailableSchedule retrieveWeeklySchedule(final IScheduleOwner owner, final Date weekOf) {
        Date weekStart = DateUtils.truncate(weekOf, Calendar.DATE);
        Date weekEnd = DateUtils.addDays(weekStart, 7);
        return retrieve(owner, weekStart, weekEnd);
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#retrieveTargetBlock(org.jasig.schedassist.model.IScheduleOwner, java.util.Date)
     */
    @Override
    public AvailableBlock retrieveTargetBlock(final IScheduleOwner owner, final Date startDate) {
        SortedSet<AvailableBlock> expanded = retrieveBlocksForDayInternal(owner, startDate);
        // truncate startDate to the second
        final Date truncatedStart = DateUtils.truncate(startDate, Calendar.MINUTE);

        if (expanded.size() > 0) {
            for (Iterator<AvailableBlock> expandedIterator = expanded.iterator(); expandedIterator.hasNext();) {
                AvailableBlock block = expandedIterator.next();
                if (block.getStartTime().equals(truncatedStart)) {
                    // always return preferred minimum length block
                    return block;
                }
                if (block.getStartTime().after(truncatedStart)) {
                    // iterated past the target block, can short circuit and say we didn't find it
                    return null;
                }
            }
        }
        // block not found, return null
        return null;
    }

    /* (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#retrieveTargetBlock(org.jasig.schedassist.model.IScheduleOwner, java.util.Date, java.util.Date)
     */
    @Override
    public AvailableBlock retrieveTargetBlock(IScheduleOwner owner, Date startDate, Date endDate) {
        SortedSet<AvailableBlock> expanded = retrieveBlocksForDayInternal(owner, startDate);
        // truncate startDate and endDate to the second
        final Date truncatedStart = DateUtils.truncate(startDate, Calendar.MINUTE);
        final Date truncatedEnd = DateUtils.truncate(endDate, Calendar.MINUTE);

        if (expanded.size() > 0) {
            for (Iterator<AvailableBlock> expandedIterator = expanded.iterator(); expandedIterator.hasNext();) {
                AvailableBlock block = expandedIterator.next();
                if (block.getStartTime().equals(truncatedStart)) {
                    if (block.getEndTime().equals(truncatedEnd)) {
                        return block;
                    }
                    // start time matches but end time doesn't
                    // check to see if the next block has matching end
                    if (expandedIterator.hasNext()) {
                        AvailableBlock nextBlock = expandedIterator.next();
                        if (nextBlock.getEndTime().equals(truncatedEnd)) {
                            // start and end represent a double length block, combine and return
                            AvailableBlock combined = AvailableBlockBuilder.createBlock(block.getStartTime(),
                                    nextBlock.getEndTime(), block.getVisitorLimit(), block.getMeetingLocation());
                            return combined;
                        }
                    }
                }

                if (block.getStartTime().after(truncatedStart)) {
                    // iterated past the target block, can short circuit and say we didn't find it
                    return null;
                }
            }
        }
        // block not found, return null
        return null;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.impl.owner.AvailableScheduleDao#retrieveTargetDoubleLengthBlock(org.jasig.schedassist.model.IScheduleOwner, java.util.Date)
     */
    @Override
    public AvailableBlock retrieveTargetDoubleLengthBlock(IScheduleOwner owner, Date startDate) {
        SortedSet<AvailableBlock> expanded = retrieveBlocksForDayInternal(owner, startDate);
        // truncate startDate to the second
        final Date truncatedStart = DateUtils.truncate(startDate, Calendar.MINUTE);

        if (expanded.size() > 0) {
            for (Iterator<AvailableBlock> expandedIterator = expanded.iterator(); expandedIterator.hasNext();) {
                AvailableBlock block = expandedIterator.next();
                if (block.getStartTime().equals(truncatedStart)) {
                    if (owner.getPreferredMeetingDurations().isDoubleLength() && expandedIterator.hasNext()) {
                        // combine the block with the next
                        AvailableBlock nextBlock = expandedIterator.next();
                        AvailableBlock combined = AvailableBlockBuilder.createBlock(block.getStartTime(),
                                nextBlock.getEndTime(), block.getVisitorLimit(), block.getMeetingLocation());
                        return combined;
                    }
                }

                if (block.getStartTime().after(truncatedStart)) {
                    // iterated past the target block, can short circuit and say we didn't find it
                    return null;
                }
            }
        }
        // block not found, return null
        return null;
    }

    /**
     * {@link AvailableBlock}s are combined before storage. The purpose of this method is to provide a consistent mechanism
     * for retrieving the block data for a specific day and expand them to the owner's preferred min duration.
     * 
     * @param owner
     * @param referenceDay
     * @return a possibly empty but never null {@link SortedSet} of {@link AvailableBlock}s with minimum preferred duration for the specified owner and calendar day.
     */
    protected SortedSet<AvailableBlock> retrieveBlocksForDayInternal(IScheduleOwner owner, Date referenceDay) {
        // retrieve all blocks for the day.
        Date startOfDay = DateUtils.truncate(referenceDay, Calendar.DATE);
        Date endOfDay = DateUtils.addDays(startOfDay, 1);
        List<PersistenceAvailableBlock> scheduleRows = this.simpleJdbcTemplate.query(
                "select * from schedules where owner_id = ? and start_time >= ? and end_time < ?",
                new PersistenceAvailableBlockRowMapper(), owner.getId(), startOfDay, endOfDay);
        SortedSet<AvailableBlock> availableBlocks = new TreeSet<AvailableBlock>();
        for (PersistenceAvailableBlock row : scheduleRows) {
            availableBlocks.add(AvailableBlockBuilder.createBlock(row.getStartTime(), row.getEndTime(),
                    row.getVisitorLimit(), row.getMeetingLocation()));
        }

        int ownerPreferredMinDuration = owner.getPreferredMeetingDurations().getMinLength();
        SortedSet<AvailableBlock> expanded = AvailableBlockBuilder.expand(availableBlocks,
                ownerPreferredMinDuration);
        return expanded;
    }

    /**
     * Remove blocks from the schedules table from all owners that have endTimes prior
     * to "<daysPrior argument> before today".
     * 
     * @return the number of blocks removed by this operation
     */
    @Transactional
    @Override
    public int purgeExpiredBlocks(final Integer daysPrior) {
        final String propertyValue = System.getProperty("org.jasig.schedassist.runScheduledTasks", "true");
        if (Boolean.parseBoolean(propertyValue)) {
            Date priorTo = DateUtils.truncate(DateUtils.addDays(new Date(), -daysPrior), Calendar.DATE);
            int rowCount = this.simpleJdbcTemplate.update("delete from schedules where end_time < ?", priorTo);
            LOG.warn("purged " + rowCount + " rows from schedules table with end_time values prior to: " + priorTo);
            return rowCount;
        } else {
            LOG.debug("ignoring purgeExpiredBlocks as 'org.jasig.schedassist.runScheduledTasks' set to false");
            return 0;
        }
    }

    /**
     * Executes "insert into schedules (schedule_id, start_time, end_time, visitor_limit) values (?, ?, ?, ?)".
     * 
     * @param scheduleBlock
     * @return the number of rows affected (should be 1 on success)
     */
    protected int internalStoreBlock(final PersistenceAvailableBlock scheduleBlock) {
        try {
            return this.simpleJdbcTemplate.update(
                    "insert into schedules (owner_id, start_time, end_time, visitor_limit, meeting_location) values (?, ?, ?, ?, ?)",
                    scheduleBlock.getOwnerId(), scheduleBlock.getStartTime(), scheduleBlock.getEndTime(),
                    scheduleBlock.getVisitorLimit(), scheduleBlock.getMeetingLocation());
        } catch (DataIntegrityViolationException e) {
            LOG.warn("ignoring attempt to insert duplicate row", e);
            return 0;
        }
    }

    /**
     * Inserts all of the arguments into the schedules table using
     * {@link SimpleJdbcTemplate#batchUpdate(String, SqlParameterSource[])}.
     * 
     * @param blocks
     */
    protected void internalStoreBlocks(final Set<PersistenceAvailableBlock> blocks) {
        SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(blocks.toArray());
        this.simpleJdbcTemplate.batchUpdate(
                "insert into schedules (owner_id, start_time, end_time, visitor_limit, meeting_location) values (:ownerId, :startTime, :endTime, :visitorLimit, :meetingLocation)",
                batch);
    }

    /**
     * Retrieve ALL {@link AvailableBlock}s for an owner in a {@link SortedSet}.
     * 
     * The blocks are returned as-is (no expansion).
     * 
     * @param owner
     * @return 
     */
    protected SortedSet<AvailableBlock> internalRetrieveSchedule(final IScheduleOwner owner) {
        List<PersistenceAvailableBlock> scheduleRows = this.simpleJdbcTemplate.query(
                "select * from schedules where owner_id = ?", new PersistenceAvailableBlockRowMapper(),
                owner.getId());

        SortedSet<AvailableBlock> availableBlocks = new TreeSet<AvailableBlock>();
        for (PersistenceAvailableBlock row : scheduleRows) {
            availableBlocks.add(AvailableBlockBuilder.createBlock(row.getStartTime(), row.getEndTime(),
                    row.getVisitorLimit(), row.getMeetingLocation()));
        }

        return availableBlocks;
    }

    /**
     * Retrieve the {@link AvailableBlock}s between the specified dates for an owner in a {@link SortedSet}.
     * 
     * Starts by retrieving all rows for the owner, then calculating the subSet between the start and end dates.
     * 
     * @param owner
     * @param startDate
     * @param endDate
     * @return
     */
    protected SortedSet<AvailableBlock> internalRetrieveSchedule(final IScheduleOwner owner, final Date startDate,
            final Date endDate) {
        TreeSet<AvailableBlock> allStoredBlocks = new TreeSet<AvailableBlock>();
        allStoredBlocks.addAll(internalRetrieveSchedule(owner));
        TreeSet<AvailableBlock> expanded = new TreeSet<AvailableBlock>();
        expanded.addAll(AvailableBlockBuilder.expand(allStoredBlocks, 1));

        // we need the subset of blocks
        AvailableBlock startBlock = AvailableBlockBuilder.createSmallestAllowedBlock(startDate);
        AvailableBlock endBlock = AvailableBlockBuilder.createBlockEndsAt(endDate, 1);
        NavigableSet<AvailableBlock> innerSet = expanded.subSet(startBlock, true, endBlock, true);
        // combine the inner set before returning
        SortedSet<AvailableBlock> combinedInnerSet = AvailableBlockBuilder.combine(innerSet);
        return combinedInnerSet;
    }

    /**
     * Deletes all existing stored blocks and inserts all specified blocks.
     * 
     * @param owner
     * @param blocks
     */
    private void replaceSchedule(final IScheduleOwner owner, final SortedSet<AvailableBlock> blocks) {
        LOG.debug("replacing schedule for owner " + owner + "; argument contains " + blocks.size() + " blocks");
        // delete all old blocks
        int rowsUpdated = this.simpleJdbcTemplate.update("delete from schedules where owner_id = ?", owner.getId());
        LOG.debug("deleted " + rowsUpdated + " for owner " + owner.getId());

        // persist the recombined set
        SortedSet<AvailableBlock> combined = AvailableBlockBuilder.combine(blocks);
        LOG.debug("combined set for owner contains " + combined.size() + " blocks");
        Set<PersistenceAvailableBlock> persistenceBlocks = new HashSet<PersistenceAvailableBlock>();
        for (AvailableBlock newBlock : combined) {
            PersistenceAvailableBlock p = new PersistenceAvailableBlock(newBlock, owner.getId());
            persistenceBlocks.add(p);
        }
        internalStoreBlocks(persistenceBlocks);

        LOG.warn("schedule replaced for owner " + owner);
        if (null != applicationEventPublisher) {
            AvailableScheduleChangedEvent e = new AvailableScheduleChangedEvent(new AvailableSchedule(blocks),
                    owner);
            applicationEventPublisher.publishEvent(e);
        }
    }
}