Java tutorial
/** * SAHARA Scheduling Server * * Schedules and assigns local laboratory rigs. * * @license See LICENSE in the top level directory for complete license terms. * * Copyright (c) 2010, University of Technology, Sydney * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the University of Technology, Sydney nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @author Michael Diponio (mdiponio) * @date 15th November 2010 */ package au.edu.uts.eng.remotelabs.schedserver.bookings.impl.slotsengine; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import au.edu.uts.eng.remotelabs.schedserver.bookings.impl.BookingNotification; import au.edu.uts.eng.remotelabs.schedserver.bookings.impl.slotsengine.MBooking.BType; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.dao.RigDao; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.Bookings; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.MatchingCapabilities; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.RequestCapabilities; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.ResourcePermission; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.Rig; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.RigOfflineSchedule; import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.RigType; import au.edu.uts.eng.remotelabs.schedserver.logger.Logger; import au.edu.uts.eng.remotelabs.schedserver.logger.LoggerActivator; /** * The bookings for a day. * <br /> * This class is not thread safe and must be externally synchronized before * invoking any of it's methods. */ public class DayBookings { /** Loaded rig bookings. */ private final Map<String, RigBookings> rigBookings; /** Type rig targets. */ private final Map<String, RigBookings> typeTargets; /** Loaded request capabilities. */ private final Map<String, RigBookings> capsTargets; /** The day key of this day. */ private final String day; /** The beginning time of this day. */ private final Date dayBegin; /** The end time of this day. */ private final Date dayEnd; /** Whether a full load is needed. */ private boolean hasFullLoad = false; /** Logger. */ private Logger logger; public DayBookings(String day) { this.logger = LoggerActivator.getLogger(); this.logger.debug("Loading day bookings for day " + day + '.'); this.rigBookings = new HashMap<String, RigBookings>(); this.day = day; this.dayBegin = TimeUtil.getDayBegin(this.day).getTime(); this.dayEnd = TimeUtil.getDayEnd(this.day).getTime(); this.typeTargets = new HashMap<String, RigBookings>(); this.capsTargets = new HashMap<String, RigBookings>(); } /* ========================================================================= * == Booking creation and management. == * ========================================================================= */ /** * Creates a booking for the rig. This methods assumes it has free reign * to choose which rig that matches the booked resource may be scheduled * in the day. Therefore a multi-day rig type or request capabilities * booking must be converted to an appropriate rig booking. * * @param mb memory booking * @param ses database session * @return */ public boolean createBooking(MBooking mb, Session ses) { RigBookings rb, next; switch (mb.getType()) { case RIG: rb = this.getRigBookings(mb.getRig(), ses); if (rb.areSlotsFree(mb)) { return rb.commitBooking(mb); } /* No directly free slots are found try a load balancing run to * free some slots. */ return this.outerLoadBalance(rb, mb, false) && this.outerLoadBalance(rb, mb, true) && rb.commitBooking(mb); case TYPE: RigType rt = mb.getRigType(); if ((rb = this.typeTargets.get(rt.getName())) == null) { Set<Rig> rigs = rt.getRigs(); if (rigs.size() == 0) { this.logger.info("Cannot make a booking for the rig type " + rt.getName() + " because it has " + "no rigs."); return false; } rb = this.getRigBookings(rigs.iterator().next(), ses); } this.typeTargets.put(rt.getName(), rb.getTypeLoopNext()); next = rb; do { if (next.areSlotsFree(mb)) { return next.commitBooking(mb); } next = next.getTypeLoopNext(); } while (next != rb); /* No directly free slots are found try a load balancing run to * free some slots. */ next = rb; do { if (this.outerLoadBalance(next, mb, false)) { return this.outerLoadBalance(next, mb, true) && next.commitBooking(mb); } next = next.getTypeLoopNext(); } while (next != rb); break; case CAPABILITY: RequestCapabilities caps = mb.getRequestCapabilities(); if ((rb = this.capsTargets.get(caps.getCapabilities())) == null) { List<RequestCapabilities> capsList = new ArrayList<RequestCapabilities>(); capsList.add(caps); this.loadRequestCapabilities(capsList, ses, false); if ((rb = this.capsTargets.get(caps.getCapabilities())) == null) { this.logger.info("Cannot make a booing for the request capabilities " + caps.getCapabilities() + " because it has no matching rigs."); return false; } } this.capsTargets.put(caps.getCapabilities(), rb.getCapsLoopNext(caps)); next = rb; do { if (next.areSlotsFree(mb)) { return next.commitBooking(mb); } next = next.getCapsLoopNext(caps); } while (next != rb); /* No directly free slots are found try a load balancing run to * free some slots. */ next = rb; do { if (this.outerLoadBalance(next, mb, false)) { return this.outerLoadBalance(next, mb, true) && next.commitBooking(mb); } next = next.getCapsLoopNext(caps); } while (next != rb); break; } return false; } /** * Finds the best fits for booking. This will generally attempt to provide * an early solution and a late solution. * * @param mb booking that couldn't be committed * @param ses * @return * @todo Implement balance operation in determining best fits */ public List<MRange> findBestFits(MBooking mb, Session ses) { /////////////////////////////////////////////////////////////////////// // DODGY: Does not do a balance operation to work out best fits // /////////////////////////////////////////////////////////////////////// MRange ef = null, lf = null; RigBookings rb, next; switch (mb.getType()) { case RIG: rb = this.getRigBookings(mb.getRig(), ses); ef = rb.getEarlyFit(mb); lf = rb.getLateFit(mb); break; case TYPE: if ((next = rb = this.typeTargets.get(mb.getRigType().getName())) == null) { Set<Rig> rigs = mb.getRigType().getRigs(); if (rigs.size() == 0) return Collections.<MRange>emptyList(); next = rb = this.getRigBookings(rigs.iterator().next(), ses); } do { ef = this.compareBestFits(mb, ef, next.getEarlyFit(mb), true); lf = this.compareBestFits(mb, lf, next.getLateFit(mb), false); next = next.getTypeLoopNext(); } while (next != rb); break; case CAPABILITY: RequestCapabilities caps = mb.getRequestCapabilities(); if ((next = rb = this.capsTargets.get(caps.getCapabilities())) == null) { List<RequestCapabilities> capsList = new ArrayList<RequestCapabilities>(); capsList.add(caps); this.loadRequestCapabilities(capsList, ses, false); if ((next = rb = this.capsTargets.get(caps.getCapabilities())) == null) return Collections.<MRange>emptyList(); } do { ef = this.compareBestFits(mb, ef, next.getEarlyFit(mb), true); lf = this.compareBestFits(mb, lf, next.getLateFit(mb), false); next = next.getCapsLoopNext(caps); } while (next != rb); break; } List<MRange> fits = new ArrayList<MRange>(2); if (ef != null) fits.add(ef); if (lf != null) fits.add(lf); return fits; } /** * Compares two best fits with a designated booking. The /best/ best fit * is the one which is closest to the designated booking. * * @param mb desired booking * @param r1 first fit * @param r2 second fit * @param whether the fit is early (before) or late (after) * @return the /best/ best fit */ private MRange compareBestFits(MBooking mb, MRange r1, MRange r2, boolean early) { if (r1 == null) return r2; if (r2 == null) return r1; if (early) { if (r1.getEndSlot() > r2.getEndSlot()) return r1; // r1 is closer to designated booking else if (r1.getEndSlot() < r2.getEndSlot()) return r2; // r2 is closer to designated booking else return r1.getNumSlots() > r2.getNumSlots() ? r1 : r2; // r1 is longer } else { if (r1.getStartSlot() < r2.getStartSlot()) return r1; // r1 is closer to designated booking else if (r1.getStartSlot() > r2.getStartSlot()) return r2; // r2 is closer to designated booking else return r1.getNumSlots() > r2.getNumSlots() ? r1 : r2; // r1 is longer } } /** * Gets the free slots for the rig type during the day. * * @param rigType the rig type to find free slots of * @param thres minimum number of slots required * @param ses database session * @return list of free slots */ public List<MRange> getFreeSlots(RigType rigType, int thres, Session ses) { return this.getFreeSlots(rigType, 0, SlotBookingEngine.NUM_SLOTS - 1, thres, ses); } /** * Gets the free slots for the request capabilities matching rigs during the day. * * @param reqCaps request capabilities * @param thres minimum number of slots required * @param ses database session * @return list of free slots */ public List<MRange> getFreeSlots(RequestCapabilities reqCaps, int thres, Session ses) { return this.getFreeSlots(reqCaps, 0, SlotBookingEngine.NUM_SLOTS - 1, thres, ses); } /** * Gets the free slots for the rig during day. * * @param rig the rig to find free slots of * @param thres minimum number of slots required * @param ses database session * @return list of free slots */ public List<MRange> getFreeSlots(Rig rig, int thres, Session ses) { return this.getFreeSlots(rig, 0, SlotBookingEngine.NUM_SLOTS - 1, thres, ses); } /** * Gets the free slots for the rig type between the start and end period * inclusive. * * @param rigType the rig type to find the free slots of * @param start start slot * @param end end slot * @param thres minimum number of slots required * @param ses database session * @return list of free slots */ public List<MRange> getFreeSlots(RigType rigType, int start, int end, int thres, Session ses) { RigBookings ts; if (!this.typeTargets.containsKey(rigType.getName())) { Set<Rig> rigs = rigType.getRigs(); if (rigs.size() == 0) { this.logger.info("Attempting to get the free slots of a rig type '" + rigType.getName() + "' that " + "doesn't have any rigs."); /* If the type doesn't have any rigs, then it doesn't have any * free slots. */ return Collections.emptyList(); } ts = this.getRigBookings(rigs.iterator().next(), ses); } else ts = this.typeTargets.get(rigType.getName()); /* Navigate the type resource loop to find the actual free slots. */ List<MRange> free = new ArrayList<MRange>(); RigBookings next = ts; do { free.addAll(next.getFreeSlots(start, end, thres)); next = next.getTypeLoopNext(); } while (next != ts); /* Try load balancing to freeable slots. */ for (MRange inuse : MRange.complement(MRange.collapseRange(free), this.day)) { int outerrs = inuse.getStartSlot(); /* If the seek start is after the end of the period specified, no need * to run load balancing. */ if (outerrs > end) continue; /* If the seek start is before the period specified, start at the period * specified. */ if (outerrs < start) outerrs = start; int outerre = inuse.getEndSlot(); /* If the seek end is before the beginning of the period specified, no * need to run load balancing. */ if (outerre < start) continue; /* If the seek end is after the period specified, end at the period * specified. */ if (outerre > end) outerre = end; ts = next; do { int innerrs = outerrs; while (innerrs <= outerre) { MBooking bk = next.getNextBooking(innerrs); if (bk == null) break; /* There isn't much point trying to load balance a type booking, * because in enclosing range, the type loop is saturated. */ if (bk.getType() == BType.CAPABILITY && this.innerLoadBalance(next, bk, false)) { free.add(new MRange(bk.getStartSlot(), bk.getEndSlot(), bk.getDay())); /* No need to further load balance bookings on other rigs * when they the slots have already been found to be freeable. */ outerrs = bk.getEndSlot() + 1; } innerrs = bk.getEndSlot() + 1; } next = next.getTypeLoopNext(); } while (ts != next); if (end < inuse.getEndSlot()) break; } return MRange.collapseRange(free); } /** * Gets the free slots for the request capabilities matching rigs between the * start and end period inclusive. * * @param reqCaps request capabilities * @param thres minimum number of slots required * @param ses database session * @return list of free slots */ public List<MRange> getFreeSlots(RequestCapabilities reqCaps, int start, int end, int thres, Session ses) { if (!this.capsTargets.containsKey(reqCaps.getCapabilities())) { List<RequestCapabilities> capsList = new ArrayList<RequestCapabilities>(); capsList.add(reqCaps); this.loadRequestCapabilities(capsList, ses, false); } RigBookings cs = this.capsTargets.get(reqCaps.getCapabilities()); if (cs == null) { /* No rigs match the request capabilities, so there can't be any * free slots. */ return Collections.<MRange>emptyList(); } List<MRange> free = new ArrayList<MRange>(); /* Navigate the capabilities loop to find the actual free slots. */ RigBookings next = cs; do { free.addAll(next.getFreeSlots(start, end, thres)); next = next.getCapsLoopNext(reqCaps); } while (cs != next); /* Try load balancing to find freeable slots. */ for (MRange inuse : MRange.complement(MRange.collapseRange(free), this.day)) { int outerrs = inuse.getStartSlot(); /* If the seek start is after the end of the period specified, no need * to run load balancing. */ if (outerrs > end) continue; /* If the seek start is before the period specified, start at the period * specified. */ if (outerrs < start) outerrs = start; int outerre = inuse.getEndSlot(); /* If the seek end is before the beginning of the period specified, no * need to run load balancing. */ if (outerre < start) continue; /* If the seek end is after the period specified, end at the period * specified. */ if (outerre > end) outerre = end; cs = next; do { int innerrs = outerrs; while (innerrs <= outerre) { MBooking bk = next.getNextBooking(innerrs); if (bk == null) break; if (this.innerLoadBalance(next, bk, false)) { free.add(new MRange(bk.getStartSlot(), bk.getEndSlot(), bk.getDay())); } innerrs = bk.getEndSlot() + 1; } next = next.getCapsLoopNext(reqCaps); } while (cs != next); if (end < inuse.getEndSlot()) break; } return MRange.collapseRange(free); } /** * Gets the free slots for the rig between the start and end period * inclusive. * * @param rig the rig to find free slots of * @param start start slot * @param end end slot * @param thres minimum number of slots required * @param ses database session * @return list of free slots */ public List<MRange> getFreeSlots(Rig rig, int start, int end, int thres, Session ses) { RigBookings rb = this.getRigBookings(rig, ses); List<MRange> free = rb.getFreeSlots(start, end, thres); /* For the other free times, attempt to load balance the bookings off the rig. */ int fs = start; while (fs < end) { MBooking membooking = rb.getNextBooking(fs); if (membooking == null) break; /* If multi-day booking, we can't load balance in this perspective * as this could leave the rig in a inconsistent state. */ if (membooking.isMultiDay()) { fs = membooking.getEndSlot() + 1; continue; } RigBookings next; switch (membooking.getType()) { /* Rig bookings can't be loaded balanced. */ case RIG: break; case TYPE: next = rb.getTypeLoopNext(); do { if (this.innerLoadBalance(next, membooking, false)) { free.add(new MRange(membooking.getStartSlot(), membooking.getEndSlot(), this.day)); break; } next = next.getTypeLoopNext(); } while (next != rb); break; case CAPABILITY: next = rb.getCapsLoopNext(membooking.getRequestCapabilities()); do { if (this.innerLoadBalance(next, membooking, false)) { free.add(new MRange(membooking.getStartSlot(), membooking.getEndSlot(), this.day)); break; } next = next.getCapsLoopNext(membooking.getRequestCapabilities()); } while (next != rb); break; } fs = membooking.getEndSlot() + 1; } return MRange.collapseRange(free); } /** * Checks whether the rig is free between the start and end slots (inclusive). * <br /> * This method is thread safe and does not require this to be externally * synchronized. * * @param rig rig to check * @param start start slot to check * @param end end slot to check * @param ses database connection * @return true if rig is free */ public boolean isRigFree(Rig rig, int start, int end, Session ses) { RigBookings rb; if (!this.rigBookings.containsKey(rig.getName())) { synchronized (this) { rb = this.getRigBookings(rig, ses); } } else rb = this.rigBookings.get(rig.getName()); return rb.areSlotsFree(start, end); } /** * Removes the booking from this day bookings. * * @param booking booking to remove * @return true if successful */ public boolean removeBooking(MBooking booking) { /* Find the rig that has the booking. */ RigBookings rb = null; switch (booking.getType()) { case RIG: Rig rig = booking.getRig(); if (!this.rigBookings.containsKey(rig.getName())) { /* The rig isn't loaded, so no need to unload it. */ return true; } rb = this.rigBookings.get(rig.getName()); break; case TYPE: RigType rigType = booking.getRigType(); if (!this.typeTargets.containsKey(rigType.getName())) { /* The type isn't loaded so need to remove a booking from * it. */ return true; } rb = this.typeTargets.get(rigType.getName()); RigBookings next = rb; do { if (next.hasBooking(booking)) { rb = next; break; } next = next.getTypeLoopNext(); } while (next != rb); break; case CAPABILITY: RequestCapabilities reqCaps = booking.getRequestCapabilities(); if (!this.capsTargets.containsKey(reqCaps.getCapabilities())) { /* The capability isn't loaded so no need to remove a * booking from it. */ return true; } rb = this.capsTargets.get(reqCaps.getCapabilities()); next = rb; do { if (next.hasBooking(booking)) { rb = next; break; } next = next.getCapsLoopNext(reqCaps); } while (rb != next); break; } return rb.removeBooking(booking); } /* ========================================================================= * == Booking redeeming operations. == * ========================================================================= */ /** * Returns the bookings starting on the specified slot and the rigs they * are allocated to. * * @param slot bookings slots are starting on * @return list of bookings with current assigned rigs */ public Map<String, MBooking> getSlotStartingBookings(int slot) { Map<String, MBooking> starting = new HashMap<String, MBooking>(); MBooking mb; for (RigBookings rb : this.rigBookings.values()) { if ((mb = rb.getSlotBooking(slot)) != null && mb.getStartSlot() == slot && mb.getSession() == null) { if (mb.isMaintenanceHolder()) continue; starting.put(rb.getRig().getName(), mb); } } return starting; } /** * Gets the booking on the specified rig and slot. * * @param rig rig to get booking * @param slot the slot to get booking from * @return booking or null if non-exists */ public MBooking getBookingOnSlot(Rig rig, int slot) { if (this.rigBookings.containsKey(rig.getName())) { return this.rigBookings.get(rig.getName()).getSlotBooking(slot); } return null; } /** * Finds a rig that matches the booking constraints and is viable (i.e. is * ready to be allocated to a session. * * @param current rig assigned to booking which is assumed to be offline * @param mb memory booking * @param ses database session * @return viable matching rig or null if not found */ public Rig findViableRig(String current, MBooking mb, Session ses) { /* Rig bookings cannot be balanced to other rigs, so we can immediately * give a no success response. */ if (mb.getType() == BType.RIG) return null; RigDao dao = new RigDao(ses); Rig currentRig = dao.findByName(current); if (currentRig == null) { this.logger.error("Attempt to find a viable rig for booking failed because rig " + current + " was not " + "found. Serious state loss."); return null; } RigBookings rb = this.getRigBookings(currentRig, ses), next; switch (mb.getType()) { case TYPE: next = rb.getTypeLoopNext(); while (next != rb) { if (this.outerLoadBalance(next, mb, false)) { Rig viable = (Rig) ses.merge(next.getRig()); ses.refresh(viable); if (viable.isActive() && viable.isOnline() && !viable.isInSession()) { this.logger.debug("Viable load balancing found rig " + viable.getName() + " can satisfy " + "booking for rig type " + mb.getRigType().getName() + '.'); this.outerLoadBalance(next, mb, true); next.commitBooking(mb); rb.removeBooking(mb); return viable; } } next = next.getTypeLoopNext(); } break; case CAPABILITY: next = rb.getCapsLoopNext(mb.getRequestCapabilities()); while (next != rb) { if (this.outerLoadBalance(next, mb, false)) { Rig viable = (Rig) ses.merge(next.getRig()); if (viable.isActive() && viable.isOnline() && !viable.isInSession()) { this.logger.debug("Viable load balancing found rig " + viable.getName() + " can satisfy " + "booking for request capabilities " + mb.getRequestCapabilities().getCapabilities() + '.'); this.outerLoadBalance(next, mb, true); next.commitBooking(mb); rb.removeBooking(mb); return viable; } } next = next.getCapsLoopNext(mb.getRequestCapabilities()); } break; } return null; } /** * Extends the existing booking on the rig. * * @param rig rig which has the booking * @param mb booking that is extended * @param ses database session * @return true if successful */ public boolean extendBooking(Rig rig, MBooking mb, Session ses) { return this.getRigBookings(rig, ses).extendBooking(mb); } /* ======================================================================== * == Resource mappings management. == * ======================================================================== */ /** * Loads all the bookings for the day into memory. * * @return ses database session */ @SuppressWarnings("unchecked") public void fullLoad(Session ses) { if (this.hasFullLoad) return; int num = 0; /* Load all the rigs that have bookings today. */ for (Rig rig : (List<Rig>) ses.createCriteria(Rig.class).list()) { if (this.rigBookings.containsKey(rig.getName())) continue; if ((num = (Integer) ses.createCriteria(Bookings.class).add(Restrictions.eq("active", Boolean.TRUE)) .add(this.addDayRange()).add(Restrictions.eq("resourceType", ResourcePermission.RIG_PERMISSION)) .add(Restrictions.eq("rig", rig)).setProjection(Projections.rowCount()).uniqueResult()) == 0) continue; this.logger.debug( "Rig " + rig.getName() + " has " + num + " bookings, so loading it up for full day load."); this.getRigBookings(rig, ses); } /* Load all the rig types that have bookings today. */ Criteria qu = ses.createCriteria(RigType.class); if (this.typeTargets.size() > 0) qu.add(Restrictions.not(Restrictions.in("name", this.typeTargets.keySet()))); for (RigType type : (List<RigType>) qu.list()) { if (this.typeTargets.containsKey(type.getName())) continue; if ((num = (Integer) ses.createCriteria(Bookings.class).add(Restrictions.eq("active", Boolean.TRUE)) .add(this.addDayRange()) .add(Restrictions.eq("resourceType", ResourcePermission.TYPE_PERMISSION)) .add(Restrictions.eq("rigType", type)).setProjection(Projections.rowCount()) .uniqueResult()) == 0) continue; this.logger.debug("Rig type " + type.getName() + " has " + num + " bookings, so loading it up for" + " full day load."); Set<Rig> rigs = type.getRigs(); if (rigs.size() == 0) { this.logger.warn("Rig type " + type.getName() + " has " + num + " bookings but not rigs so they all" + " will be cancelled."); for (Bookings bk : (List<Bookings>) ses.createCriteria(Bookings.class) .add(Restrictions.eq("active", Boolean.TRUE)).add(this.addDayRange()) .add(Restrictions.eq("resourceType", ResourcePermission.TYPE_PERMISSION)) .add(Restrictions.eq("rigType", type)).list()) { this.logger.warn("Cancelling booking for " + bk.getUser().qName() + " because booking rig type " + type.getName() + " has no rigs."); bk.setActive(false); bk.setCancelReason("Booked rig type has no rigs."); new BookingNotification(bk).notifyCancel(); } ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); continue; } this.getRigBookings(rigs.iterator().next(), ses); } /* Load all the request capabilities that have bookings today. */ qu = ses.createCriteria(RequestCapabilities.class); if (this.capsTargets.size() > 0) qu.add(Restrictions.not(Restrictions.in("capabilities", this.capsTargets.keySet()))); for (RequestCapabilities caps : (List<RequestCapabilities>) qu.list()) { if (this.capsTargets.containsKey(caps.getCapabilities())) continue; if ((num = (Integer) ses.createCriteria(Bookings.class).add(Restrictions.eq("active", Boolean.TRUE)) .add(this.addDayRange()) .add(Restrictions.eq("resourceType", ResourcePermission.CAPS_PERMISSION)) .add(Restrictions.eq("requestCapabilities", caps)).setProjection(Projections.rowCount()) .uniqueResult()) == 0) continue; this.logger.debug("Request capabilities " + caps.getCapabilities() + " has " + num + " bookings, so " + "loading it up for full day load."); List<RequestCapabilities> capsList = new ArrayList<RequestCapabilities>(); capsList.add(caps); this.loadRequestCapabilities(capsList, ses, true); if (!this.capsTargets.containsKey(caps.getCapabilities())) { this.logger.warn("Request capabilities " + caps.getCapabilities() + " has " + num + " bookings but " + "not any matching rigs so they all will be cancelled."); for (Bookings bk : (List<Bookings>) ses.createCriteria(Bookings.class) .add(Restrictions.eq("active", Boolean.TRUE)).add(this.addDayRange()) .add(Restrictions.eq("resourceType", ResourcePermission.CAPS_PERMISSION)) .add(Restrictions.eq("requestCapabilities", caps)).list()) { this.logger.warn("Cancelling booking for " + bk.getUser().qName() + " because booking request " + "capabilities " + caps.getCapabilities() + " has no matching rigs."); bk.setActive(false); bk.setCancelReason("Booked request capabilities has no rigs."); new BookingNotification(bk).notifyCancel(); } ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); } } } /** * Outer load balance. Load balancing is trying to fit the specified booking * onto the rig. * * @param rb rig to balance to * @param mb booking to balance onto rig * @param doCommit whether to actually commit the changes. * @return true if successful */ private boolean outerLoadBalance(RigBookings rb, MBooking mb, boolean doCommit) { RigBookings next; /* We need to rule a load balance pass to try and free up * space for the booking. */ MBooking ex = rb.getNextBooking(mb.getStartSlot()); boolean found = false; while (ex != null && ex.getStartSlot() <= mb.getEndSlot()) { found = false; switch (ex.getType()) { case RIG: return false; case TYPE: next = rb.getTypeLoopNext(); this.logger.debug("Starting outer load balance with the Rig - " + next.getRig().getName()); do { if (this.innerLoadBalance(next, ex, doCommit)) { found = true; if (doCommit) { rb.removeBooking(ex); next.commitBooking(ex); } break; } next = next.getTypeLoopNext(); } while (next != rb); if (!found) return false; break; case CAPABILITY: next = rb.getCapsLoopNext(ex.getRequestCapabilities()); do { if (this.innerLoadBalance(next, ex, doCommit)) { found = true; if (doCommit) { rb.removeBooking(ex); next.commitBooking(ex); } break; } next = next.getCapsLoopNext(ex.getRequestCapabilities()); } while (next != rb); if (!found) return false; } ex = rb.getNextBooking(ex.getEndSlot() + 1); } return true; } /** * Runs load balancing of a rig. Load balancing attempts to fit the * provided booking onto the specified rig. The booking may be fitted * if the slots are free or the bookings in the slot range can be * provided to an equivalent matching rig. * * @param rb the rig bookings to free the slots * @param bk bookings to try and fit it * @param doCommit whether the load balancing will actually be committed * @return true if successfully able to load balance */ private boolean innerLoadBalance(RigBookings br, MBooking bk, boolean doCommit) { int start = bk.getStartSlot(); while (start <= bk.getEndSlot()) { MBooking ex = br.getNextBooking(start); if (ex == null || ex.getStartSlot() > bk.getEndSlot()) { /* We have already finished balancing all the required bookings, * so no more work to do. */ return true; } /* We will not balance any cross-day bookings. The reason is, * cross day bookings need to be balancing in sequence with * the other days rigs. */ if (ex.isMultiDay()) return false; /* We will not balance bookings that have already been redeemed, * because they (obviously) can't be moved. */ if (ex.getSession() != null) return false; boolean hasBalanced = false; RigBookings next; switch (ex.getType()) { case RIG: /* Unable to move rig bookings. They are fixed to the * booked rig. */ return false; case TYPE: /* Run through the type loop to try to balance the booking. */ next = br.getTypeLoopNext(); while (next != br) { if (next.areSlotsFree(ex)) { /* Found free slots. */ if (doCommit) { this.logger.debug("Balancing type booking from rig " + br.getRig().getName() + " to rig " + next.getRig().getName() + "."); br.removeBooking(ex); next.commitBooking(ex); } start = ex.getEndSlot() + 1; hasBalanced = true; break; } next = next.getTypeLoopNext(); } /* We weren't able to balance booking so fail load balance to * this rig. */ if (!hasBalanced) return false; break; case CAPABILITY: RequestCapabilities reqCaps = ex.getRequestCapabilities(); /* Run through the capability loop to try to balance the booking. */ next = br.getCapsLoopNext(reqCaps); while (next != br) { if (next.areSlotsFree(ex)) { /* Found free slots. */ if (doCommit) { this.logger.debug("Balancing capability booking from rig " + br.getRig().getName() + " to rig " + next.getRig().getName() + "."); br.removeBooking(ex); next.commitBooking(ex); } start = ex.getEndSlot() + 1; hasBalanced = true; break; } next = next.getCapsLoopNext(reqCaps); } /* We weren't able to balance booking so fail load balance to * this rig. */ if (!hasBalanced) return false; break; } } return true; } /** * Gets the rig bookings for the rig. If the rig bookings hasn't been * loaded for the rig, it is loaded by: * <ul> * <li>Loading the rig and its booking for the day.</li> * <li>Loading all the rigs in the rig type and their bookings.</li> * <li>Linking the rig type resource loop.</li> * <li>Loading the rig type bookings and assigning them to a rig.</li> * <li>Loading the request capabilities resource loops for those that * have at least one booking.</li> * <li>Loading the request capabilities bookings.</li> * </ul> * * @param rig rig to find bookings of * @return rig bookings */ private RigBookings getRigBookings(Rig rig, Session ses) { if (!this.rigBookings.containsKey(rig.getName())) { this.logger.debug("Loaded day bookings for rig '" + rig.getName() + "' on day " + this.day + "."); List<RequestCapabilities> capsToLoad = new ArrayList<RequestCapabilities>(); RigBookings rb = new RigBookings(rig, this.day); this.loadRig(rb, rig, ses); this.rigBookings.put(rig.getName(), rb); /* Add the capabilities that need to be loaded. */ for (MatchingCapabilities match : rig.getRigCapabilities().getMatchingCapabilitieses()) { capsToLoad.add(match.getRequestCapabilities()); } this.loadRigType(rig, ses, capsToLoad); /* Load the request capabilities resource loops for those that have * bookings. */ this.loadRequestCapabilities(capsToLoad, ses, true); } return this.rigBookings.get(rig.getName()); } /** * A rig has been registered. A registered rig may: * <ul> * <li>Be newly registered.</li> * <li>Have a changed type or rig capabilities from a previous registration.</li> * <li>Be the same as a previous registration.</li> * <ul> * * @param rig rig that was registered * @param ses database session */ public void rigRegistered(Rig rig, Session ses) { String rigName = rig.getName(); String rigType = rig.getRigType().getName(); List<RequestCapabilities> rigCaps = new ArrayList<RequestCapabilities>(); for (MatchingCapabilities match : rig.getRigCapabilities().getMatchingCapabilitieses()) { rigCaps.add(match.getRequestCapabilities()); } if (this.rigBookings.containsKey(rigName)) { RigBookings rb = this.rigBookings.get(rigName); /* Check the type loop to see whether we are member of the correct type * loop. */ if (!rb.getRigType().equals(rigType)) { /* The rig changed type so we must remove the rig from it current type * resource loop and it add it to the new rig resource loop. */ this.logger.debug("Registered rig " + rigName + " has a different type then when it was last " + "registered so changing type mapping."); if (rb.getTypeLoopNext() == rb) { /* The type just contains the rig so remove the type loop all together. */ this.typeTargets.remove(rb.getRigType()); /* All type bookings assigned to the rig need to be cancelled * because the type no longer exists. */ List<MBooking> typeb = rb.getTypeBookings(); for (MBooking mb : typeb) { /* In session type bookings have already been allocated * to the rig so cannot be moved. */ if (mb.getSession() != null) continue; this.logger.warn("Cancelling type booking (" + mb.getBooking().getId() + ") because the " + "previous assigned rig " + rigName + " has changed type."); rb.removeBooking(mb); Bookings b = (Bookings) ses.merge(mb.getBooking()); b.setActive(false); b.setCancelReason("Rig no longer in rig type."); new BookingNotification(b).notifyCancel(); } if (typeb.size() > 0) { ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); } } else { RigBookings prev = rb.getTypeLoopNext(), next = prev.getTypeLoopNext(); while (next != rb) { prev = next; next = next.getTypeLoopNext(); } prev.setTypeLoopNext(rb.getTypeLoopNext()); if (this.typeTargets.get(prev.getRigType()) == rb) this.typeTargets.put(prev.getRigType(), prev); /* For all the type bookings, try to put the booking on * a different rig. */ List<MBooking> typeb = rb.getTypeBookings(); boolean requiresFlush = false; for (MBooking mb : typeb) { /* In session type bookings have already been allocated * to the rig so cannot be moved. */ if (mb.getSession() != null) continue; rb.removeBooking(mb); Bookings b = (Bookings) ses.merge(mb.getBooking()); mb.setBooking(b); if (!this.createBooking(mb, ses)) { requiresFlush = true; b.setActive(false); b.setCancelReason("Type over booked because rig no longer in rig type."); new BookingNotification(b).notifyCancel(); } } if (requiresFlush) { ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); } } /* Add the rig to the type resource loop. */ rb.setRigType(rigType); if (this.typeTargets.containsKey(rigType)) { /* The rig type loop already exists so add the registered * rig to it. */ RigBookings tt = this.typeTargets.get(rigType); rb.setTypeLoopNext(tt.getTypeLoopNext()); tt.setTypeLoopNext(rb); } else { this.loadRigType(rig, ses, new ArrayList<RequestCapabilities>()); } } /* Make sure all the capability resource loops are correct for the * registered rig. */ List<String> currentCaps = rb.getCapabilities(); Iterator<RequestCapabilities> it = rigCaps.iterator(); while (it.hasNext()) { RequestCapabilities reqCaps = it.next(); if (currentCaps.contains(reqCaps.getCapabilities())) { it.remove(); currentCaps.remove(reqCaps.getCapabilities()); } } /* Remove the capabilities the rig is no longer a member of. */ if (currentCaps.size() > 0) { for (String cc : currentCaps) { this.logger.debug("Rig " + rigName + " no longer has capability " + cc + " so removing it from " + "the " + cc + " resource loop."); List<MBooking> capsb = rb.getCapabilitiesBookings(cc); if (rb.getCapsLoopNext(cc) == rb) { this.capsTargets.remove(cc); /* Need to cancel all of the capabilities bookings because * the request capabilities has no more rigs. */ for (MBooking mb : capsb) { if (mb.getSession() != null) continue; rb.removeBooking(mb); Bookings b = (Bookings) ses.merge(mb.getBooking()); b.setActive(false); b.setCancelReason("Capabilities over booked because rig no longer matches capability."); new BookingNotification(b).notifyCancel(); } if (capsb.size() > 0) { ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); } } else { /* Remove the rig for the capabilities loop. */ RigBookings prev = rb.getCapsLoopNext(cc), next = prev.getCapsLoopNext(cc); while (next != rb) { prev = next; next = next.getCapsLoopNext(cc); } prev.setCapsLoopNext(cc, rb.getCapsLoopNext(cc)); /* First attempt to put the booking onto another rig. */ boolean requiresFlush = false; for (MBooking mb : capsb) { if (mb.getSession() != null) continue; rb.removeBooking(mb); Bookings b = (Bookings) ses.merge(mb.getBooking()); mb.setBooking(b); if (!this.createBooking(mb, ses)) { b.setActive(false); b.setCancelReason( "Capabilities over booked because rig no longer matches capability."); new BookingNotification(b).notifyCancel(); requiresFlush = true; } } if (requiresFlush) { ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); } } rb.removeCapsLoopNext(cc); } } /* Make sure all the remaining capabilities are loaded. For loaded * capabilities loops, insert the rig into them. */ it = rigCaps.iterator(); while (it.hasNext()) { RequestCapabilities reqCaps = it.next(); /* Caps loop is not loaded, so no need to load it. */ if (!this.capsTargets.containsKey(reqCaps.getCapabilities())) continue; RigBookings capsb = this.capsTargets.get(reqCaps.getCapabilities()); rb.setCapsLoopNext(reqCaps, capsb.getCapsLoopNext(reqCaps)); capsb.setCapsLoopNext(reqCaps, rb); it.remove(); } if (rigCaps.size() > 0) { this.loadRequestCapabilities(rigCaps, ses, true); } } else { RigBookings rb = new RigBookings(rig, this.day); /* The rig isn't registered so may either be new *OR* the resource * loops it is a member of aren't loaded. */ if (this.typeTargets.containsKey(rigType)) { /* The type is loaded so the rig is a new rig. This means we can * safely insert the rig into its matching resource loop because * there can be no incorrect mappings. */ this.rigBookings.put(rigName, rb); this.logger .debug("Registered rig " + rig.getName() + " is new and has its type resource loop loaded " + "so inserting it into the type resource loop for day " + this.day + "."); /* Insert the rig into the type resource loop. */ RigBookings typerb = this.typeTargets.get(rigType); rb.setTypeLoopNext(typerb.getTypeLoopNext()); typerb.setTypeLoopNext(rb); /* If any of the request capabilities are loaded, add to rig * to them. */ for (RequestCapabilities caps : rigCaps) { if (this.capsTargets.containsKey(caps.getCapabilities())) { this.logger.debug("Registered rig " + rigName + " is new and has its matching capability '" + caps.getCapabilities() + "' resource loop loaded so inserting the rig in the " + "capability resource loop for day " + this.day + "."); RigBookings capsrb = this.capsTargets.get(caps.getCapabilities()); rb.setCapsLoopNext(caps, capsrb.getCapsLoopNext(caps)); capsrb.setCapsLoopNext(caps, rb); } } } else { /* The rig can be either new or existing but unloaded in this day. * Either way, we check the capabilities loops and if the rig matches * at least one, we add the rig to it and do a load of the type. */ boolean hasMatch = false; Iterator<RequestCapabilities> it = rigCaps.iterator(); while (it.hasNext()) { RequestCapabilities caps = it.next(); if (this.capsTargets.containsKey(caps.getCapabilities())) { this.logger.debug("Registered rig " + rigName + " is new and has its matching capability '" + caps.getCapabilities() + "' resource loop loaded so inserting the rig in the " + "capability resource loop."); hasMatch = true; RigBookings capsrb = this.capsTargets.get(caps.getCapabilities()); rb.setCapsLoopNext(caps, capsrb.getCapsLoopNext(caps)); capsrb.setCapsLoopNext(caps, rb); it.remove(); } } if (hasMatch) { this.rigBookings.put(rigName, rb); this.loadRigType(rig, ses, rigCaps); this.loadRequestCapabilities(rigCaps, ses, true); } } } } /** * Makes the rig unavailable for the scheduled offline period. If there * are already assigned bookings for the rigs, these will be either moved * to a different rig or cancelled if no matching rig is found. * <br /> * This works <em>lazily</em> so if the rig isn't loaded, it will not be * loaded and marked offline. * * @param off offline period * @param ses database session */ public void putRigOffline(RigOfflineSchedule off, Session ses) { if (!(this.rigBookings.containsKey(off.getRig().getName()))) { this.logger.debug("Not committing offline period for rig " + off.getRig().getName() + " because the rig " + "isn't loaded."); return; } RigBookings rb = this.getRigBookings(off.getRig(), ses); MBooking mb = new MBooking(off, this.day); if (mb.getEndSlot() < 0) return; /* Get the the bookings on the rig that already exist. These will need * to be moved or will be canceled. */ int ss = mb.getStartSlot(); List<MBooking> oldBookings = new ArrayList<MBooking>(); while (ss <= mb.getDuration()) { MBooking ex = rb.getNextBooking(ss); if (ex == null) break; rb.removeBooking(ex); oldBookings.add(ex); ss = ex.getEndSlot() + 1; } /* Commit the maintenance holding booking. */ rb.commitBooking(mb); /* Move or cancel the old bookings. */ boolean hasCanceled = false; for (MBooking ex : oldBookings) { if (ex.getBooking() == null) continue; if (!this.createBooking(ex, ses)) { Bookings booking = (Bookings) ses.merge(ex.getBooking()); this.logger.warn("Canceling booking (ID " + booking.getId() + ") for user " + booking.getUser().qName() + " because the assigned rig " + off.getRig().getName() + " will be offline and there are no " + "other rigs which can take the booking."); booking.setActive(false); booking.setCancelReason("Rig will be offline for reservation."); hasCanceled = true; new BookingNotification(booking).notifyCancel(); } } if (hasCanceled) { /* Commit the cancellations. */ ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); } } /** * Clears the rig from being unavailable for the schedule offline period. * * @param off offline period * @param ses database session */ public void clearRigOffline(RigOfflineSchedule off, Session ses) { if (!this.rigBookings.containsKey(off.getRig().getName())) return; RigBookings rb = this.getRigBookings(off.getRig(), ses); MBooking mb = rb.getSlotBooking(TimeUtil.getDaySlotIndex(off.getStartTime(), this.day)); if (mb == null || !mb.isMaintenanceHolder()) { this.logger.error("Cancelling a rig offline period for rig " + off.getRig().getName() + " but the " + "maintenance holding was not found."); } else rb.removeBooking(mb); } /** * Loads the request capabilities. * * @param capsList capabilities list * @param ses database session */ private void loadRequestCapabilities(List<RequestCapabilities> capsList, Session ses, boolean ignoreNoBookings) { while (capsList.size() > 0) { RequestCapabilities reqCaps = capsList.get(0); this.logger.debug( "Attempting to load bookings for request capabilities " + reqCaps.getCapabilities() + '.'); Criteria qu = ses.createCriteria(Bookings.class) .add(Restrictions.eq("resourceType", ResourcePermission.CAPS_PERMISSION)) .add(Restrictions.eq("requestCapabilities", reqCaps)) .add(Restrictions.eq("active", Boolean.TRUE)).add(this.addDayRange()) .addOrder(Order.desc("duration")); @SuppressWarnings("unchecked") List<Bookings> bookings = qu.list(); /* There are no bookings for this class so no need to load it * yet. */ if (ignoreNoBookings && bookings.size() == 0) { this.logger.debug("Not going to load request capabilities " + reqCaps.getCapabilities() + " because " + "it has no bookings."); capsList.remove(0); continue; } /* Find all the matching rigs. */ List<Rig> matchingRigs = new ArrayList<Rig>(); for (MatchingCapabilities match : reqCaps.getMatchingCapabilitieses()) { matchingRigs.addAll(match.getRigCapabilities().getRigs()); } /* If the request capabilities has no matching rigs, we cannot load * the request capabilities loop. */ if (matchingRigs.size() == 0) { this.logger.debug("Cannot load up request capbilities resource loop for '" + reqCaps.getCapabilities() + "' because it has no matching rigs."); capsList.remove(0); continue; } /* Make sure all the rigs are loaded. */ for (Rig r : matchingRigs) { if (!this.rigBookings.containsKey(r.getName())) { RigBookings b = new RigBookings(r, this.day); this.loadRig(b, r, ses); this.rigBookings.put(r.getName(), b); /* By definition, since a rig wasn't loaded, it's type wasn't * loaded either. */ this.loadRigType(r, ses, capsList); } } /* Complete the request capabilities resource loop. */ RigBookings first = this.rigBookings.get(matchingRigs.get(0).getName()); RigBookings prev = first; for (int i = 1; i < matchingRigs.size(); i++) { RigBookings next = this.rigBookings.get(matchingRigs.get(i).getName()); prev.setCapsLoopNext(reqCaps, next); prev = next; } prev.setCapsLoopNext(reqCaps, first); /* Load the request capabilities bookings. */ for (Bookings booking : bookings) { MBooking membooking = new MBooking(booking, this.day); RigBookings next = first; do { if (next.areSlotsFree(membooking)) { if (next.commitBooking(membooking)) { /* If there is a next booking, try load it to the next rig. */ first = next.getCapsLoopNext(reqCaps); break; } else { this.logger.error( "Failed to commit a booking to a slots that should have been empty. This " + "is a probable race condition. Ominous, but the loading search will continue regardless."); } } next = next.getCapsLoopNext(reqCaps); } while (next != first); /* The assignment loop was completed and no position was found to put * the booking, so run load balance to try to free a resource. */ if (!next.hasBooking(membooking)) { do { if (this.innerLoadBalance(next, membooking, true)) { if (next.commitBooking(membooking)) { /* If there is a next booking, try load it to the next rig. */ first = next.getCapsLoopNext(reqCaps); break; } else { this.logger .error("Failed to commit a booking to a slots that should have been empty. " + "This is a probable race condition. Ominous, but the loading search will " + "continue regardless."); } } next = next.getCapsLoopNext(reqCaps); } while (next != first); } /* The balancing loop was completed and no position was found to put * the booking, so the booking will need to be canceled. */ if (!next.hasBooking(membooking)) { this.logger.error("Request capabilities '" + reqCaps.getCapabilities() + "' is over commited " + "and has over lapping bookings. The booking for '" + booking.getUserNamespace() + ':' + booking.getUserName() + "' starting at " + booking.getStartTime() + " is being cancelled."); booking.setActive(false); booking.setCancelReason("Request capabilities was overbooked."); ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); new BookingNotification(booking).notifyCancel(); } } this.capsTargets.put(capsList.remove(0).getCapabilities(), prev); } } /** * Loads a rig type by creating the rig bookings type loop, then loading * the rig type bookings to rigs. * * @param rig rig in type * @param ses database session * @param capsToLoad list of request capabilities that may need to be loaded from * the rigs in type */ @SuppressWarnings("unchecked") private void loadRigType(Rig rig, Session ses, List<RequestCapabilities> capsToLoad) { RigBookings first = this.rigBookings.get(rig.getName()); RigBookings prev = first; /* Set up the rig type navigation loop. */ RigType rigType = rig.getRigType(); this.logger.debug("Loading rig type " + rigType.getName() + " for day " + this.day + '.'); Set<Rig> rigs = rigType.getRigs(); for (Rig r : rigs) { for (MatchingCapabilities match : r.getRigCapabilities().getMatchingCapabilitieses()) { RequestCapabilities reqCaps = match.getRequestCapabilities(); if (!capsToLoad.contains(reqCaps)) { capsToLoad.add(reqCaps); } } /* Don't duplicate the initial rig. */ if (r.equals(rig)) continue; RigBookings next = new RigBookings(r, this.day); this.loadRig(next, r, ses); this.rigBookings.put(r.getName(), next); prev.setTypeLoopNext(next); prev = next; } /* Complete the type loop. */ prev.setTypeLoopNext(first); /* Load up the type bookings. */ Criteria qu = ses.createCriteria(Bookings.class) .add(Restrictions.eq("resourceType", ResourcePermission.TYPE_PERMISSION)) .add(Restrictions.eq("rigType", rigType)).add(Restrictions.eq("active", Boolean.TRUE)) .add(this.addDayRange()).addOrder(Order.desc("duration")); for (Bookings booking : (List<Bookings>) qu.list()) { MBooking membooking = new MBooking(booking, this.day); RigBookings next = first; do { if (next.areSlotsFree(membooking)) { if (next.commitBooking(membooking)) { /* If there is a next booking, try load it to the next rig. */ first = next.getTypeLoopNext(); break; } else { this.logger.error("Failed to commit a booking to a slots that should have been empty. This " + "is a probable race condition. Ominous, but the loading search will continue regardless."); } } next = next.getTypeLoopNext(); } while (next != first); /* The assignment loop was completed and no position was found to put * the booking, so run load balance to try to free a resource. */ if (!next.hasBooking(membooking)) { do { if (this.innerLoadBalance(next, membooking, true)) { if (next.commitBooking(membooking)) { /* If there is a next booking, try load it to the next rig. */ first = next.getTypeLoopNext(); break; } else { this.logger.error("Failed to commit a booking to a slots that should have been empty. " + "This is a probable race condition. Ominous, but the loading search will " + "continue regardless."); } } next = next.getTypeLoopNext(); } while (next != first); } /* The balancing loop was completed and no position was found to put * the booking, so the type was over-booked. The booking will need to be canceled. */ if (!next.hasBooking(membooking)) { this.logger.error( "Rig type '" + rigType.getName() + "' is over commited and has over lapping bookings. " + "The booking for '" + booking.getUserNamespace() + ':' + booking.getUserName() + "' starting at " + booking.getStartTime() + " is being cancelled."); booking.setActive(false); booking.setCancelReason("Rig type was overbooked."); ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); new BookingNotification(booking).notifyCancel(); } } this.typeTargets.put(rigType.getName(), first); } /** * Loads the rig booking times for the rig. If the rig is over committed * (overlapping rig bookings), one of the bookings is canceled. * * @param bookings rig bookings container * @param rig rig to load bookings from * @param ses database session */ @SuppressWarnings("unchecked") private void loadRig(RigBookings bookings, Rig rig, Session ses) { /* Load the rig offline periods. */ Criteria qu = ses.createCriteria(RigOfflineSchedule.class).add(Restrictions.eq("active", Boolean.TRUE)) .add(Restrictions.eq("rig", rig)).add(this.addDayRange()); for (RigOfflineSchedule off : (List<RigOfflineSchedule>) qu.list()) { bookings.commitBooking(new MBooking(off, this.day)); } /* Load the rigs bookings. */ qu = ses.createCriteria(Bookings.class) .add(Restrictions.eq("resourceType", ResourcePermission.RIG_PERMISSION)) .add(Restrictions.eq("rig", rig)).add(Restrictions.eq("active", Boolean.TRUE)) .add(this.addDayRange()); for (Bookings booking : (List<Bookings>) qu.list()) { if (!bookings.commitBooking(new MBooking(booking, this.day))) { /* The rig has been over booked so this booking will * need to be canceled. */ this.logger.error("Rig '" + rig.getName() + "' is over commited and has over lapping bookings. " + "The booking for '" + booking.getUserNamespace() + ':' + booking.getUserName() + "' starting at " + booking.getStartTime() + " is being cancelled."); booking.setActive(false); booking.setCancelReason("Rig will be offline or was overbooked."); ses.beginTransaction(); ses.flush(); ses.getTransaction().commit(); new BookingNotification(booking).notifyCancel(); } } } /** * Adds a day range constraint to a bookings query so that bookings within * this day are returned. * * @return restriction */ private Criterion addDayRange() { return Restrictions.disjunction().add(Restrictions.and( // Booking within day Restrictions.ge("startTime", this.dayBegin), Restrictions.le("endTime", this.dayEnd))) .add(Restrictions.and( // Booking starts before day and ends on this day Restrictions.lt("startTime", this.dayBegin), Restrictions.gt("endTime", this.dayBegin))) .add(Restrictions.and( // Booking starts on day and ends after day Restrictions.lt("startTime", this.dayEnd), Restrictions.gt("endTime", this.dayEnd))) .add(Restrictions.and(Restrictions.le("startTime", this.dayBegin), Restrictions.gt("endTime", this.dayEnd))); } public String getDay() { return this.day; } }