/*
* 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.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.persist.FacilityAvailability;
import de.knurt.fam.core.model.persist.User;
import de.knurt.fam.core.persistence.dao.FamDaoProxy;
import de.knurt.fam.core.util.booking.ApplicationConflicts;
import de.knurt.fam.core.util.booking.BookingIsAvailableDeciderProxy;
import de.knurt.fam.core.util.booking.TimeBookingRequest;
import de.knurt.fam.core.util.mail.OutgoingUserMailBox;
import de.knurt.fam.core.view.text.FamDateFormat;
import de.knurt.fam.core.view.text.FamText;
import de.knurt.heinzelmann.util.time.SimpleTimeFrame;
import de.knurt.heinzelmann.util.time.TimeFrame;
/**
* a data holder for booking a facility at a specific time.
*
* the booking is made from a user for a specific facility and time frame. it is
* not a definitive booking but has a booking status.
*
* @see BookingStatus
* @author Daniel Oltmanns
* @since 0.20090615
*/
public final class TimeBooking extends AbstractBooking implements TimeFrame {
/** {@inheritDoc} */
@Override
public TimeFrame clone() {
return (TimeFrame) super.clone();
}
private TimeFrame timeFrame;
/**
* return true, if the session of this booking is already made. this session
* is made, if the requested time frame ends. it does not matter, if the
* booking is an application or it is cancelled.
*
* @return true, if the session of this booking is already made.
*/
@Override
public boolean sessionAlreadyMade() {
TimeFrame tmp = this.getSessionTimeFrame();
return tmp != null && tmp.endsInPast();
}
/**
* return true, if the session started in past.
*
* @return true, if the session started in past.
*/
@Override
public boolean sessionAlreadyBegun() {
TimeFrame tmp = this.getSessionTimeFrame();
return tmp != null && tmp.startsInPast();
}
/**
* return a new {@link TimeBooking} from a {@link TimeBookingRequest} with
* an unset {@link BookingStatus}.
*
* this means, all {@link BookingStatus} objects of Bookings must be set
* afterwards, when {@link TimeBooking} shall be insert.
*
* if given request is invalid, return null.
*
* @see BookingStatus#STATUS_UNSET
* @param bookingRequest
* a new booking is created from
* @return a new {@link TimeBooking} from a {@link TimeBookingRequest} with
* an unset {@link BookingStatus}.
*/
public static TimeBooking getNewBooking(TimeBookingRequest bookingRequest) {
if (bookingRequest.isValidRequest()) {
FacilityBookable facility = bookingRequest.getFacility();
User user = bookingRequest.getUser();
TimeFrame tf = bookingRequest.getRequestedTimeFrame();
BookingStatus bs = new BookingStatus(BookingStatus.STATUS_UNSET);
Integer capacityUnits = bookingRequest.getRequestedCapacityUnits();
BookingRule bookingRule = bookingRequest.getBookingRule();
return new TimeBooking(facility, user, tf, bs, capacityUnits, bookingRule);
} else {
return null;
}
}
/** {@inheritDoc} */
@Override
public String getArticleNumber() {
return this.getUsername() + this.getCapacityUnits() + this.getFacilityKey() + this.getStart() + this.getEnd();
}
/** {@inheritDoc} */
@Override
public boolean isAvailableForInsertion() {
return BookingIsAvailableDeciderProxy.me().getDeciderFactory().get(this.getFacility()).isAvailableForInsertion(this);
}
/**
* cancel the booking with given cancelation.
*
* @param cancelation
* used to cancel the booking.
*/
@Override
public void cancel(Cancelation cancelation) {
this.setCancelation(cancelation);
this.update();
OutgoingUserMailBox.insert_BookingCancelation(this);
}
/**
* return true, if this booking overlaps with a unavailability set. it
* overlaps it with the session time frame.
*
* @see #getSessionTimeFrame()
* @return true, if this booking overlaps with a unavailability set.
*/
public boolean isApplicableToANotAvailableFacilityAvailability() {
return this.isApplicableToANotAvailableFacilityAvailability(this.getSessionTimeFrame());
}
public boolean isApplicableToANotAvailableFacilityAvailability(TimeFrame supposedTimeFrame) {
boolean result = false;
List<FacilityAvailability> availabilities = FamDaoProxy.facilityDao().getFacilityAvailabilitiesMergedByFacilities(supposedTimeFrame, this.getFacilityKey());
for (FacilityAvailability availability : availabilities) {
boolean isNotBookable = availability.isNotAvailableInGeneral() || availability.isNotAvailableBecauseOfMaintenance() || availability.isNotAvailableBecauseOfSuddenFailure() || availability.mustNotStartHere();
if (isNotBookable) {
if (availability.applicableTo(supposedTimeFrame)) {
result = true;
break;
}
}
}
return result;
}
/**
* return true, if this booking is available on given time frame. it is not,
* if ...
* <ul>
* <li>starting time of given time frame is in past</li>
* <li>it is booked out</li>
* <li>the facility is generaly not available</li>
* <li>the user has to wait for the facility some time</li>
* </ul>
* assume, that you are not booked and not in the database. or answer the
* question "am i available to insert me into the db?"
*
* @param supposedTimeFrame
* to check
* @return true, if this booking is available, if it has the given time
* frame.
*/
public boolean isAvailableForInsertion(TimeFrame supposedTimeFrame) {
return BookingIsAvailableDeciderProxy.me().getDeciderFactory().get(this.getFacility()).isAvailableForInsertion(this, supposedTimeFrame);
}
/**
* return TimeBooking for querying by example.
*
* @return TimeBooking for querying by example.
*/
public static TimeBooking getEmptyExampleBooking() {
return new TimeBooking();
}
/**
* return a {@link TimeBooking} for queries. this is not for persistent use
* but to get some out of the db.
*
* @param start
* time of booking starts
* @param end
* time of booking ends
* @see TimeFrame#setEnd(long)
* @see TimeFrame#setStart(long)
* @return a {@link TimeBooking} for queries.
*/
public static TimeBooking getBooking4Query(long start, long end) {
TimeBooking result = new TimeBooking();
result.timeFrame = new SimpleTimeFrame(start, end);
return result;
}
/**
* construct a {@link TimeBooking}.
*
* @see BookingRule#smallestBookableCapacityUnit
* @param facility
* the booking is for.
* @param user
* interested the facility
* @param tf
* the user is interested in
* @param bs
* status of booking.
* @param capacityUnits
* booked
* @param bookingRule
* used for booking
*/
private TimeBooking(FacilityBookable facility, User user, TimeFrame tf, BookingStatus bs, int capacityUnits, BookingRule bookingRule) {
this(facility.getKey(), user.getUsername(), tf.getStart(), tf.getEnd(), bs, capacityUnits, bookingRule);
}
private TimeBooking(String facilityKey, String username, long start, long end, BookingStatus bookingStatus, int capacityUnits, BookingRule bookingRule) {
super(facilityKey, username, bookingStatus, capacityUnits, bookingRule);
this.timeFrame = new SimpleTimeFrame(start, end);
}
/**
* construct empty booking (for querying reason)
*/
protected TimeBooking() {
}
/** {@inheritDoc} */
@Override
public synchronized boolean insert() throws DataIntegrityViolationException {
boolean result = FamDaoProxy.bookingDao().insert(this);
if (this.isUncanceledBooking()) {
// send mail to booker
OutgoingUserMailBox.insert_BookingMade(this);
// cancel applications for same slot
Cancelation cancelation = new Cancelation(this.getUser(), Cancelation.REASON_BOOKED_BY_ANOTHER);
this.cancelApplicationsForSameSlotIfNotAvailableAnymore(cancelation);
}
return result;
}
private synchronized boolean update(boolean cancelUncanceledBookingsForSameSlot) throws DataIntegrityViolationException {
boolean result = FamDaoProxy.bookingDao().update(this);
if (cancelUncanceledBookingsForSameSlot) {
// cancel applications for same slot
Cancelation cancelation = new Cancelation(this.getUser(), Cancelation.REASON_BOOKED_BY_ANOTHER);
this.cancelApplicationsForSameSlotIfNotAvailableAnymore(cancelation);
}
return result;
}
/** {@inheritDoc} */
@Override
public boolean update() throws DataIntegrityViolationException {
return this.update(this.isUncanceledBooking());
}
private boolean isUncanceledBooking() {
return this.getBookingStatus().isBooked() && this.isCanceled() == false;
}
private void cancelApplicationsForSameSlotIfNotAvailableAnymore(Cancelation cancelation) {
List<Booking> bookings = FamDaoProxy.bookingDao().getUncanceledBookingsAndApplicationsIn(this.getFacility(), this);
for (Booking booking : bookings) {
if (booking.getId().intValue() == this.getId().intValue()) { // hello
// me!
continue; // do not cancel me
}
if (booking.isApplication() && !booking.isAvailableForInsertion()) { // is
// not
// available
// anymore
booking.cancel(cancelation);
booking.update();
}
}
}
/**
* return true if the session time frame requested contains the given date.
*
* @param date
* to check
* @return true if the session time frame requested contains the given date.
*/
@Override
public boolean contains(Date date) {
return this.timeFrame.contains(date);
}
/**
* add the given time to requested time frame's start.
*
* @see TimeFrame#add(int, int)
* @param field
* the amount is added to
* @param amount
* to be added to the given time frame
*/
@Override
public void add(int field, int amount) {
this.timeFrame.add(field, amount);
}
/**
* forward {@link TimeFrame#setCalendarStart(int, int)} of requested
* session.
*
* @param field
* the amount is added to
* @param amount
* to be added to the given time frame
*/
@Override
public void setCalendarStart(int field, int amount) {
this.timeFrame.setCalendarStart(field, amount);
}
/**
* forward {@link TimeFrame#setCalendarEnd(int, int)} of requested session.
*
* @param field
* the amount is added to
* @param amount
* to be added to the given time frame
*/
@Override
public void setCalendarEnd(int field, int amount) {
this.timeFrame.setCalendarEnd(field, amount);
}
/**
* return true, if {@link TimeFrame#startsInPast()} of requested session.
*
* @return true, if {@link TimeFrame#startsInPast()} of requested session.
*/
@Override
public boolean startsInPast() {
return this.timeFrame.startsInPast();
}
/**
* return true, if
* {@link TimeFrame#overlaps(de.knurt.heinzelmann.util.time.TimeFrame)} of
* requested session.
*
* @param timeFrame
* to check
* @return true, if
* {@link TimeFrame#overlaps(de.knurt.heinzelmann.util.time.TimeFrame)}
* of requested session.
*/
@Override
public boolean overlaps(TimeFrame timeFrame) {
return this.timeFrame.overlaps(timeFrame);
}
/**
* return {@link TimeFrame#getStart()} of requested session.
*
* @return {@link TimeFrame#getStart()} of requested session.
*/
@Override
public long getStart() {
return this.timeFrame.getStart();
}
/**
* return {@link TimeFrame#getDateStart()} of requested session.
*
* @return {@link TimeFrame#getDateStart()} of requested session.
*/
@Override
public Date getDateStart() {
return this.timeFrame.getDateStart();
}
/**
* return {@link TimeFrame#getCalendarStart()} of requested session.
*
* @return {@link TimeFrame#getCalendarStart()} of requested session.
*/
@Override
public Calendar getCalendarStart() {
return this.timeFrame.getCalendarStart();
}
/**
* return {@link TimeFrame#getCalendarEnd()} of requested session.
*
* @return {@link TimeFrame#getCalendarEnd()} of requested session.
*/
@Override
public Calendar getCalendarEnd() {
return this.timeFrame.getCalendarEnd();
}
/**
* return {@link TimeFrame#getDateEnd()} of requested session.
*
* @return {@link TimeFrame#getDateEnd()} of requested session.
*/
@Override
public Date getDateEnd() {
return this.timeFrame.getDateEnd();
}
/**
* set {@link TimeFrame#setStartEnd(long, long)} of requested session.
*
* @param start
* of {@link TimeFrame#setStartEnd(long, long)}
* @param end
* of {@link TimeFrame#setStartEnd(long, long)}
*/
@Override
public void setStartEnd(long start, long end) {
this.timeFrame.setStartEnd(start, end);
}
/**
* set
* {@link TimeFrame#setStartEnd(de.knurt.heinzelmann.util.time.TimeFrame)}
* of requested session.
*
* @param timeFrame
* of
* {@link TimeFrame#setStartEnd(de.knurt.heinzelmann.util.time.TimeFrame)}
*/
@Override
public void setStartEnd(TimeFrame timeFrame) {
this.timeFrame.setStartEnd(timeFrame);
}
/**
* return {@link TimeFrame#getDateEnd()} of requested session.
*
* @return {@link TimeFrame#getDateEnd()} of requested session.
*/
@Override
public long getEnd() {
return this.timeFrame.getEnd();
}
/**
* set {@link TimeFrame#setEnd(long)} of requested session.
*
* @param end
* of {@link TimeFrame#setEnd(long)}
*/
@Override
public void setEnd(long end) {
this.timeFrame.setEnd(end);
}
/**
* set {@link TimeFrame#setStart(long)} of requested session.
*
* @param start
* of {@link TimeFrame#setStart(long)}
*/
@Override
public void setStart(long start) {
this.timeFrame.setStart(start);
}
/**
* return {@link TimeFrame#getDuration()} of requested session.
*
* @return {@link TimeFrame#getDuration()} of requested session.
*/
@Override
public long getDuration() {
return this.timeFrame.getDuration();
}
/**
* return {@link TimeFrame#endsInPast()} of requested session.
*
* @return {@link TimeFrame#endsInPast()} of requested session.
*/
@Override
public boolean endsInPast() {
return this.timeFrame.endsInPast();
}
/**
* return
* {@link TimeFrame#compareTo(de.knurt.heinzelmann.util.time.TimeFrame)} of
* requested session.
*
* @param o
* to compare
* @return {@link TimeFrame#compareTo(de.knurt.heinzelmann.util.time.TimeFrame)}
* of requested session.
*/
@Override
public int compareTo(TimeFrame o) {
return this.timeFrame.compareTo(o);
}
/**
* forward {@link TimeFrame#addEnd(int, int)} of requested session.
*
* @param field
* for {@link TimeFrame#addEnd(int, int)}
* @param amount
* for {@link TimeFrame#addEnd(int, int)}
*/
@Override
public void addEnd(int field, int amount) {
this.timeFrame.addEnd(field, amount);
}
/**
* forward {@link TimeFrame#addStart(int, int)} of requested session.
*
* @param field
* for {@link TimeFrame#addStart(int, int)}
* @param amount
* for {@link TimeFrame#addStart(int, int)}
*/
@Override
public void addStart(int field, int amount) {
this.timeFrame.addStart(field, amount);
}
/**
* set {@link TimeFrame#setStart(java.util.Calendar)} of requested session.
*
* @param calendar
* of {@link TimeFrame#setStart(java.util.Calendar)}
*/
@Override
public void setStart(Calendar calendar) {
this.timeFrame.setStart(calendar);
}
/**
* set {@link TimeFrame#setEnd(java.util.Calendar)} of requested session.
*
* @param calendar
* of {@link TimeFrame#setEnd(java.util.Calendar)}
*/
@Override
public void setEnd(Calendar calendar) {
this.timeFrame.setEnd(calendar);
}
/**
* set {@link TimeFrame#setStart(Date)} of requested session.
*
* @param start
* of {@link TimeFrame#setStart(Date)}
*/
@Override
public void setStart(Date start) {
this.timeFrame.setStart(start);
}
/**
* set {@link TimeFrame#setEnd(Date)} of requested session.
*
* @param calendar
* of {@link TimeFrame#setEnd(Date)}
*/
@Override
public void setEnd(Date end) {
this.timeFrame.setEnd(end);
}
/**
* return the time frame requested with this time booking.
*
* @return the time frame requested with this time booking.
*/
@Override
public TimeFrame getSessionTimeFrame() {
return this.timeFrame;
}
/**
* after purchasing this booking (which means, it is booked), return a
* message for it.
*
* @return return a message after purchasing this booking
*/
@Override
public String getMessageAfterTryingToPurchase() {
return this.messageAfterTryingToPurchase;
}
private String messageAfterTryingToPurchase;
/**
* return true, if this booking has been purchased (which means set as
* booked or applied). before purchasing it, check, if it is still available
* and generate the message for it.
*
* @return true, if this booking has been purchased (which means set as
* booked or applied).
*/
@Override
public boolean purchase() {
boolean result = false;
this.messageAfterTryingToPurchase = "";
if (!this.startsInPast() && this.isCompletelyAvailable()) {
if (FamAuth.hasRight(this.getUser(), FamAuth.DIRECT_BOOKING, this.getFacility())) {
this.setBooked();
} else {
this.setApplied();
}
try {
this.insert();
String format = "We got your %s for %s on<br />%s"; // INTLANG
this.messageAfterTryingToPurchase = String.format(format, this.isApplication() ? "Application" : "Booking", FamText.facilityNameWithCapacityUnits(this), FamDateFormat.getDateFormattedWithTime(this, true)); // INTLANG
result = true;
} catch (DataIntegrityViolationException ex) {
}
} else if (!this.isCompletelyAvailable()) {
this.messageAfterTryingToPurchase = "The time slot, you are queried for, has just been booked by someone else"; // INTLANG
} else { // wait to long
this.messageAfterTryingToPurchase = "The time slot, you are queried for, is past in meanwhile."; // INTLANG
}
return result;
}
/**
* set it processed and send a mail to the user.
*/
@Override
public void processSession() {
this.setProcessed();
this.update(false);
OutgoingUserMailBox.insert_BookingProcessed(this);
}
/** {@inheritDoc} */
@Override
public List<Booking> getConflicts() {
return ApplicationConflicts.getInstance().getConflicts(this);
}
}