All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jasig.schedassist.impl.owner.SpringJDBCAvailableScheduleDaoImpl Maven / Gradle / Ivy

There is a newer version: 1.1.4
Show newest version
/**
 * 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, [email protected]
 * @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 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 blocks) {
		// retrieve existing schedule
		AvailableSchedule stored = retrieve(owner);

		// expand it to minimum possible size
		SortedSet storedExpanded = AvailableBlockBuilder.expand(stored.getAvailableBlocks(), 1);
		// expand the argument to minimum possible size blocks
		SortedSet 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 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 blocksToRemove) {
		// retrieve existing schedule
		AvailableSchedule stored = retrieve(owner);

		// expand it to minimum possible size
		SortedSet expanded = AvailableBlockBuilder.expand(stored.getAvailableBlocks(), 1);
		// expand the argument to minimum possible size blocks
		SortedSet 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 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 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) {
		// truncate startDate to the second
		final Date truncatedStart = DateUtils.truncate(startDate, Calendar.MINUTE);

		// retrieve all blocks for the day.
		Date startOfDay = DateUtils.truncate(startDate, Calendar.DATE);
		Date endOfDay = DateUtils.addDays(startOfDay, 1);
		List scheduleRows = this.simpleJdbcTemplate
		.query("select * from schedules where owner_id = ? and start_time >= ? and end_time < ?", 
				new PersistenceAvailableBlockRowMapper(), 
				owner.getId(),
				startOfDay,
				endOfDay);
		SortedSet availableBlocks = new TreeSet();
		for(PersistenceAvailableBlock row : scheduleRows) {
			availableBlocks.add(AvailableBlockBuilder.createBlock(row.getStartTime(), row.getEndTime(), row.getVisitorLimit(), row.getMeetingLocation()));
		}

		int ownerPreferredMinDuration = owner.getPreferredMeetingDurations().getMinLength();
		SortedSet expanded = AvailableBlockBuilder.expand(availableBlocks, ownerPreferredMinDuration);
		if(expanded.size() > 0) {
			for(Iterator expandedIterator = expanded.iterator(); expandedIterator.hasNext();) {
				AvailableBlock block = expandedIterator.next();
				if(block.getStartTime().equals(truncatedStart)) {
					// always return preferred minimum length block
					return block;
				}
			}
		}
		// 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) {
		// truncate startDate to the second
		final Date truncatedStart = DateUtils.truncate(startDate, Calendar.MINUTE);

		// retrieve all blocks for the day.
		Date startOfDay = DateUtils.truncate(startDate, Calendar.DATE);
		Date endOfDay = DateUtils.addDays(startOfDay, 1);
		List scheduleRows = this.simpleJdbcTemplate
		.query("select * from schedules where owner_id = ? and start_time >= ? and end_time < ?", 
				new PersistenceAvailableBlockRowMapper(), 
				owner.getId(),
				startOfDay,
				endOfDay);
		SortedSet availableBlocks = new TreeSet();
		for(PersistenceAvailableBlock row : scheduleRows) {
			availableBlocks.add(AvailableBlockBuilder.createBlock(row.getStartTime(), row.getEndTime(), row.getVisitorLimit(), row.getMeetingLocation()));
		}

		int ownerPreferredMinDuration = owner.getPreferredMeetingDurations().getMinLength();
		SortedSet expanded = AvailableBlockBuilder.expand(availableBlocks, ownerPreferredMinDuration);
		if(expanded.size() > 0) {
			for(Iterator 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;
					} 
				} 
			}
		}
		// block not found, return null
		return null;
	}

	/**
	 * Remove blocks from the schedules table from all owners that have endTimes prior
	 * to " 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 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 internalRetrieveSchedule(final IScheduleOwner owner) {
		List scheduleRows = this.simpleJdbcTemplate
		.query("select * from schedules where owner_id = ?", 
				new PersistenceAvailableBlockRowMapper(), 
				owner.getId());

		SortedSet availableBlocks = new TreeSet();
		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 internalRetrieveSchedule(final IScheduleOwner owner, final Date startDate, final Date endDate) {		
		TreeSet allStoredBlocks = new TreeSet();
		allStoredBlocks.addAll(internalRetrieveSchedule(owner));
		TreeSet expanded = new TreeSet();
		expanded.addAll(AvailableBlockBuilder.expand(allStoredBlocks, 1));

		// we need the subset of blocks
		AvailableBlock startBlock = AvailableBlockBuilder.createSmallestAllowedBlock(startDate);
		AvailableBlock endBlock = AvailableBlockBuilder.createBlockEndsAt(endDate, 1);
		NavigableSet innerSet = expanded.subSet(startBlock, true,
				endBlock, true);
		// combine the inner set before returning
		SortedSet 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 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 combined = AvailableBlockBuilder.combine(blocks);
		LOG.debug("combined set for owner contains " + combined.size() + " blocks");
		Set persistenceBlocks = new HashSet();
		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);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy