/* * Copyright 2009-2012 by KNURT Systeme (http://www.knurt.de) * * Licensed under the Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://creativecommons.org/licenses/by-nc-sa/3.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 de.knurt.fam.core.model.persist.booking; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.springframework.dao.DataIntegrityViolationException; import de.knurt.fam.core.aspects.security.auth.FamAuth; import de.knurt.fam.core.model.config.BookingRule; import de.knurt.fam.core.model.config.FacilityBookable; import de.knurt.fam.core.model.config.QueueBasedBookingRule; import de.knurt.fam.core.model.persist.User; import de.knurt.fam.core.persistence.dao.BookingDao; import de.knurt.fam.core.persistence.dao.FamDaoProxy; import de.knurt.fam.core.util.booking.BookingIsAvailableDeciderProxy; import de.knurt.fam.core.util.mail.OutgoingUserMailBox; import de.knurt.heinzelmann.util.time.SimpleTimeFrame; import de.knurt.heinzelmann.util.time.TimeFrame; /** * a facility where you have to join a queue for, is booked with this. this * {@link Booking} made by rules of a {@link QueueBasedBookingRule}. * * @author Daniel Oltmanns * @since 0.20090924 */ public class QueueBooking extends AbstractBooking implements Comparable<QueueBooking> { /** * return an empty {@link QueueBooking}. this must only be used for db * queries by example. * * @return an empty {@link QueueBooking}. */ public static QueueBooking getBooking4Query() { return new QueueBooking(); } /** {@inheritDoc} */ @Override public String getArticleNumber() { return this.getUsername() + this.getCapacityUnits() + this.getFacilityKey(); } /** * return an empty {@link QueueBooking}. this must only be used for db * queries by example. * * @param sessionStart * real session time frame start * @param sessionEnd * real session time frame end * @return an empty {@link QueueBooking}. */ public static QueueBooking getBooking4Query(long sessionStart, long sessionEnd) { QueueBooking result = getBooking4Query(); result.setRealSessionTimeFrame(sessionStart, sessionEnd); return result; } private QueueBooking() { } /** * construct it with given user for given facility. - status is set to * booked, if user has right for direct booking. otherwise to applied. - * capacity units of booking is set to min bookable. * * @see BookingStatus#STATUS_UNSET * @see BookingRule#getMinBookableCapacityUnits() * @see FamAuth#DIRECT_BOOKING * @param user * @param facility */ public QueueBooking(User user, FacilityBookable facility) { this.setUsername(user.getUsername()); this.setFacilityKey(facility.getKey()); if (user.hasRight(FamAuth.DIRECT_BOOKING, facility)) { this.setBooked(); } else { this.setApplied(); } this.setBookingRule(facility.getBookingRule()); this.setCapacityUnits(facility.getBookingRule().getMinBookableCapacityUnits(user)); } /** * return true, if it is possible to queue. it is not possible, if the max * length of the queue is reached * * @return true, if it is possible to queue. */ @Override public boolean isAvailableForInsertion() { return BookingIsAvailableDeciderProxy.me().getDeciderFactory().get(this.getFacility()).isAvailableForInsertion(this); } /** * return the real made session time frame (always past) * * @return the real made session time frame (always past) */ @Override public TimeFrame getSessionTimeFrame() { return this.realSessionTimeFrame; } /** * set the session time frame of the queue booking. set <code>null</code> if * session is not made yet. * * @param tf * time frame of the session */ public void setSessionTimeFrame(TimeFrame tf) { this.realSessionTimeFrame = tf; } private TimeFrame realSessionTimeFrame = null; private void setRealSessionTimeFrame(long msStart, long msEnd) { this.realSessionTimeFrame = new SimpleTimeFrame(msStart, msEnd); } /** {@inheritDoc} */ @Override public void cancel(Cancelation cancelation) { this.setCancelation(cancelation); this.update(); OutgoingUserMailBox.insert_BookingCancelation(this); this.getQueueBasedBookingRule().reduceQueue(); } /** {@inheritDoc} */ @Override public boolean sessionAlreadyMade() { return this.sessionAlreadyBegun() && this.realSessionTimeFrame.getDuration() != 0; } /** {@inheritDoc} */ @Override public boolean sessionAlreadyBegun() { return this.realSessionTimeFrame != null; } /** * return the time frame when the session is expected or a now-pointer, if * this is not part of the queue. in other words, return * <code>new SimpleTimeFrame(this.getExpectedSessionStart(), this.getExpectedSessionEnd())</code> * * if the session already made or the session is canceled, return null. * * @see #getExpectedSessionEnd() * @see #getExpectedSessionStart() * @see SimpleTimeFrame * @return the time frame when the session is expected or a now-pointer, if * this is not part of the queue. */ public TimeFrame getExpectedSessionTimeFrame() { Calendar start = this.getExpectedSessionStart(); if (start != null) { return new SimpleTimeFrame(start, this.getExpectedSessionEnd()); } else { return null; } } /** * insert into db * * @throws DataIntegrityViolationException * if it violates db rules */ @Override public boolean insert() throws DataIntegrityViolationException { FamDaoProxy.bookingDao().insert(this); this.getQueueBasedBookingRule().incrementQueue(); return OutgoingUserMailBox.insert_BookingMade(this); } public QueueBasedBookingRule getQueueBasedBookingRule() { return (QueueBasedBookingRule) this.getBookingRule(); } /** * return the expected start of the session. if the session already made or * the session is canceled, return null. * * @return the expected start of the session */ public Calendar getExpectedSessionStart() { Calendar result = null; Integer aqp = this.getCurrentQueuePosition(); if (aqp != null) { result = Calendar.getInstance(); if (aqp > 0) { aqp--; // -1: 1st position is next result.add(Calendar.MINUTE, aqp * 60 / this.getQueueBasedBookingRule().getUnitsPerHourProcessed()); } } return result; } /** * return the expected end of the session if the session already made or the * session is canceled, return null. * * @return the expeted end of the session */ public Calendar getExpectedSessionEnd() { Calendar result = this.getExpectedSessionStart(); if (result != null) { result.add(Calendar.MINUTE, 60 / this.getQueueBasedBookingRule().getUnitsPerHourProcessed()); } return result; } /** * updateAnswers in db * * @throws DataIntegrityViolationException * if it violates db rules */ @Override public boolean update() throws DataIntegrityViolationException { return FamDaoProxy.bookingDao().update(this); } /** * return success message, because never fail * * @return success message, because never fail */ @Override public String getMessageAfterTryingToPurchase() { return "You have just queued up for " + this.getFacility().getLabel() + "."; // INTLANG } /** * never fail, even if it is not available anymore. * * @return true, because never fail. */ @Override public boolean purchase() { this.insert(); return true; } /** * return the position in the queue of this booking. if this is not a part * of the current queue return queue length. * * @see BookingDao#getCurrentPositionInQueue(de.knurt.fam.core.model.persist.booking.QueueBooking) * @return the position in the queue of this booking. */ public Integer getCurrentQueuePosition() { return FamDaoProxy.bookingDao().getCurrentPositionInQueue(this); } /** * stop the session of the booking now. update session after starting. */ public void stopSession() { long start = this.realSessionTimeFrame.getStart(); long end = new Date().getTime(); this.setRealSessionTimeFrame(start, end); this.update(); this.getQueueBasedBookingRule().reduceQueue(); OutgoingUserMailBox.insert_BookingProcessed(this); } /** * start the session of the booking now. update session after starting. */ public void startSession() { long startAndEnd = new Date().getTime(); this.setRealSessionTimeFrame(startAndEnd, startAndEnd); this.update(); } /** * process the session. means set and update real session time frame. if * session has been started, take this start point. otherwise take * {@link QueueBasedBookingRule#getUnitsPerHourProcessed()} for the duration * and go back in time from now. */ @Override public void processSession() { Long start = null; if (this.sessionAlreadyBegun()) { start = this.realSessionTimeFrame.getStart(); } else { Integer uphp = this.getQueueBasedBookingRule().getUnitsPerHourProcessed(); uphp = uphp == null ? 1 : uphp; // nothing set? take 1 hour Calendar startc = Calendar.getInstance(); startc.add(Calendar.MINUTE, 60 / uphp * -1); start = startc.getTimeInMillis(); } long end = new Date().getTime(); this.setRealSessionTimeFrame(start.longValue(), end); this.setProcessed(); this.update(); this.getQueueBasedBookingRule().reduceQueue(); OutgoingUserMailBox.insert_BookingProcessed(this); } /** {@inheritDoc} */ @Override public int compareTo(QueueBooking o) { return this.getSeton().compareTo(o.getSeton()); } /** * queue bookings never conflicting */ @Override public List<Booking> getConflicts() { return new ArrayList<Booking>(); } }