au.edu.uts.eng.remotelabs.schedserver.bookings.pojo.impl.BookingsServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for au.edu.uts.eng.remotelabs.schedserver.bookings.pojo.impl.BookingsServiceImpl.java

Source

/**
 * SAHARA Scheduling Server
 *
 * Schedules and assigns local laboratory rigs.
 *
 * @license See LICENSE in the top level directory for complete license terms.
 *
 * Copyright (c) 2011, Michael Diponio
 * 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 7th August 2011
 */
package au.edu.uts.eng.remotelabs.schedserver.bookings.pojo.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import au.edu.uts.eng.remotelabs.schedserver.bookings.BookingsActivator;
import au.edu.uts.eng.remotelabs.schedserver.bookings.impl.BookingEngine;
import au.edu.uts.eng.remotelabs.schedserver.bookings.impl.BookingEngine.BookingCreation;
import au.edu.uts.eng.remotelabs.schedserver.bookings.impl.BookingEngine.TimePeriod;
import au.edu.uts.eng.remotelabs.schedserver.bookings.impl.BookingNotification;
import au.edu.uts.eng.remotelabs.schedserver.bookings.impl.slotsengine.TimeUtil;
import au.edu.uts.eng.remotelabs.schedserver.bookings.pojo.BookingsService;
import au.edu.uts.eng.remotelabs.schedserver.bookings.pojo.types.BookingOperation;
import au.edu.uts.eng.remotelabs.schedserver.bookings.pojo.types.BookingsPeriod;
import au.edu.uts.eng.remotelabs.schedserver.dataaccess.dao.BookingsDao;
import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.Bookings;
import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.ResourcePermission;
import au.edu.uts.eng.remotelabs.schedserver.dataaccess.entities.User;
import au.edu.uts.eng.remotelabs.schedserver.dataaccess.listener.BookingsEventListener.BookingsEvent;
import au.edu.uts.eng.remotelabs.schedserver.logger.Logger;
import au.edu.uts.eng.remotelabs.schedserver.logger.LoggerActivator;
import au.edu.uts.eng.remotelabs.schedserver.multisite.provider.requests.BookingsTimesRequest;
import au.edu.uts.eng.remotelabs.schedserver.multisite.provider.requests.BookingsTimesRequest.BookingTime;
import au.edu.uts.eng.remotelabs.schedserver.multisite.provider.requests.CancelBookingRequest;
import au.edu.uts.eng.remotelabs.schedserver.multisite.provider.requests.CreateBookingRequest;

/**
 * Bookings service implementation.
 */
public class BookingsServiceImpl implements BookingsService {
    /** Bookings engine. */
    private BookingEngine engine;

    /** Logger. */
    private Logger logger;

    public BookingsServiceImpl() {
        this.logger = LoggerActivator.getLogger();
        this.engine = BookingsActivator.getBookingEngine();
    }

    @Override
    public BookingsPeriod getFreeBookings(Calendar reqStart, Calendar reqEnd, ResourcePermission perm, Session db) {
        BookingsPeriod response = new BookingsPeriod();

        /* Make sure the permission a valid booking permission. */
        if (!(perm.getUserClass().isActive() && perm.getUserClass().isBookable())) {
            this.logger
                    .warn("Unable to provide free times because the permission is not a valid booking permission.");
            response.addSlot(reqStart, reqEnd, BookingsPeriod.NO_PERMISSION);
            return response;
        }

        /* ----------------------------------------------------------------
         * -- Check permission times to make sure the request is within  --
         * -- the permission start and expiry range.                     --
         * ---------------------------------------------------------------- */
        Calendar permStart = Calendar.getInstance();
        permStart.setTime(perm.getStartTime());

        /* The /actual/ permission start time may either be the permission 
         * start time or the time horizon time. This is a second offset from
         * the current time (i.e. it atleast stops bookings being made for
         * the past. */
        Calendar horizonTime = Calendar.getInstance();
        horizonTime.add(Calendar.SECOND, perm.getUserClass().getTimeHorizon());
        if (horizonTime.after(permStart)) {
            /* Which ever comes later. */
            permStart = TimeUtil.coerceToNextSlotTime(horizonTime);
        }

        Calendar permEnd = Calendar.getInstance();
        permEnd.setTime(perm.getExpiryTime());

        if (reqEnd.before(permStart) || reqStart.after(permEnd) || permEnd.before(permStart)) {
            /* In this case the requested range is, after the end of the 
             * permission region, before the start of the permission 
             * region or the permission start is after the permission end
             * (the permission start is always sliding for horizion). */
            response.addSlot(reqStart, reqEnd, BookingsPeriod.NO_PERMISSION);
            return response;
        }

        if (reqStart.before(permStart)) {
            /* Here the permission start time is after the requested start time 
             * so the start partial date has no permission. */
            response.addSlot(reqStart, permStart, BookingsPeriod.NO_PERMISSION);

            /* The permission start time is now the search start time. */
            reqStart = permStart;
        }

        Calendar searchEnd = reqEnd;
        if (reqEnd.after(permEnd)) {
            /* Here the permission end time is before the requested end time
             * so the end partial date has no permission. We will search the free
             * search end to the permission end but add the no permission period
             * last. */
            searchEnd = permEnd;
        }

        /* ----------------------------------------------------------------
         * -- Get the free times for the permission resource.            --
         * ---------------------------------------------------------------- */
        List<TimePeriod> free = null;
        if (ResourcePermission.RIG_PERMISSION.equals(perm.getType())) {
            free = this.engine.getFreeTimes(perm.getRig(), new TimePeriod(reqStart, searchEnd),
                    perm.getSessionDuration() / 2, db);
        } else if (ResourcePermission.TYPE_PERMISSION.equals(perm.getType())) {
            free = this.engine.getFreeTimes(perm.getRigType(), new TimePeriod(reqStart, searchEnd),
                    perm.getSessionDuration() / 2, db);
        } else if (ResourcePermission.CAPS_PERMISSION.equals(perm.getType())) {
            free = this.engine.getFreeTimes(perm.getRequestCapabilities(), new TimePeriod(reqStart, searchEnd),
                    perm.getSessionDuration() / 2, db);
        } else if (ResourcePermission.CONSUMER_PERMISSION.equals(perm.getType())) {
            free = new ArrayList<BookingEngine.TimePeriod>();
            /* We need to ask the remote site about times. */
            BookingsTimesRequest multiSiteReq = new BookingsTimesRequest();
            if (multiSiteReq.findFreeTimes(perm.getRemotePermission(), reqStart, searchEnd, db)) {
                /* We are integrated the multisite result set with the local 
                 * times so we are only interested in the free times. */
                for (BookingTime t : multiSiteReq.getTimes()) {
                    if (BookingsPeriod.FREE.equals(t.getState()))
                        free.add(new TimePeriod(t.getStart(), t.getEnd()));
                }
            } else {
                this.logger.warn("Unable to find free times because multisite call failed.");
            }
        }

        /* ----------------------------------------------------------------
         * -- Populate the resource with free and booked time            --
         * ---------------------------------------------------------------- */
        Calendar c = reqStart;
        if (free.size() > 0) {
            for (TimePeriod period : free) {
                if (Math.abs(period.getStartTime().getTimeInMillis() - c.getTimeInMillis()) > 60000) {
                    /* The difference with the last time and the next time is
                     * more than a minute, then there should be a booked period. */
                    response.addSlot(c, period.getStartTime(), BookingsPeriod.BOOKED);
                }

                response.addSlot(period.getStartTime(), period.getEndTime(), BookingsPeriod.FREE);
                c = period.getEndTime();
            }

            /* There is a booked spot at the end. */
            if (Math.abs(searchEnd.getTimeInMillis() - c.getTimeInMillis()) > 60000) {
                /* The difference with the last time and the next time is
                 * more than a minute, then there should be a booked period. */
                response.addSlot(c, searchEnd, BookingsPeriod.BOOKED);
            }
        } else {
            /* There is no free times on the day. */
            response.addSlot(reqStart, searchEnd, BookingsPeriod.BOOKED);
        }

        if (reqEnd.after(permEnd)) {
            /* Add a no permission at the end. */
            response.addSlot(permEnd, reqEnd, BookingsPeriod.NO_PERMISSION);
        }

        return response;
    }

    @Override
    public BookingOperation createBooking(Calendar start, Calendar end, User user, ResourcePermission perm,
            Session db) {
        BookingOperation response = new BookingOperation();
        response.setSuccess(false);

        /* ----------------------------------------------------------------
         * -- Check permission constraints.                              --
         * ---------------------------------------------------------------- */
        Date startDate = start.getTime();
        Date endDate = end.getTime();

        if (!perm.getUserClass().isBookable()) {
            this.logger.info(
                    "Unable to create a booking because the permission " + perm.getId() + " is not bookable.");
            response.setFailureReason("Permission not bookable.");
            return response;
        }

        if (startDate.before(perm.getStartTime()) || endDate.after(perm.getExpiryTime())) {
            this.logger.info("Unable to create a booking because the booking time is outside the permission time. "
                    + "Permission start: " + perm.getStartTime() + ", expiry: " + perm.getExpiryTime()
                    + ", booking start: " + startDate + ", booking end: " + endDate + '.');
            response.setFailureReason("Booking time out of permission range.");
            return response;
        }

        /* Time horizon is a moving offset when bookings can be made. */
        Calendar horizon = Calendar.getInstance();
        horizon.add(Calendar.SECOND, perm.getUserClass().getTimeHorizon());
        if (horizon.after(start)) {
            this.logger.info("Unable to create a booking because the booking start time (" + startDate
                    + ") is before the time horizon (" + horizon.getTime() + ").");
            response.setFailureReason("Before time horizon.");
            return response;
        }

        /* Maximum concurrent bookings. */
        int numBookings = (Integer) db.createCriteria(Bookings.class).add(Restrictions.eq("active", Boolean.TRUE))
                .add(Restrictions.eq("user", user)).add(Restrictions.eq("resourcePermission", perm))
                .setProjection(Projections.rowCount()).uniqueResult();
        if (numBookings >= perm.getMaximumBookings()) {
            this.logger.info("Unable to create a booking because the user " + user.getNamespace() + ':'
                    + user.getName() + " already has the maxiumum numnber of bookings (" + numBookings + ").");
            response.setFailureReason("User has maximum number of bookings.");
            return response;
        }

        /* User bookings at the same time. */
        numBookings = (Integer) db.createCriteria(Bookings.class).setProjection(Projections.rowCount())
                .add(Restrictions.eq("active", Boolean.TRUE)).add(Restrictions.eq("user", user))
                .add(Restrictions.disjunction()
                        .add(Restrictions.and(Restrictions.gt("startTime", startDate),
                                Restrictions.lt("startTime", endDate)))
                        .add(Restrictions.and(Restrictions.gt("endTime", startDate),
                                Restrictions.le("endTime", endDate)))
                        .add(Restrictions.and(Restrictions.le("startTime", startDate),
                                Restrictions.gt("endTime", endDate))))
                .uniqueResult();

        if (numBookings > 0) {
            this.logger.info("Unable to create a booking because the user " + user.getNamespace() + ':'
                    + user.getName() + " has concurrent bookings.");
            response.setFailureReason("User has concurrent bookings.");
            return response;
        }

        /* ----------------------------------------------------------------
         * -- Create booking.                                            --
         * ---------------------------------------------------------------- */
        if (ResourcePermission.CONSUMER_PERMISSION.equals(perm.getType())) {
            CreateBookingRequest request = new CreateBookingRequest();
            if (request.createBooking(user, perm.getRemotePermission(), start, end, db)) {
                response.setSuccess(request.wasSuccessful());
                response.setFailureReason(request.getReason());
                if (request.wasSuccessful()) {
                    /* Provider created booking so we now need to create it 
                     * locally. */
                    this.logger.debug("Successfullly created booking at provider with identifier "
                            + request.getBookingID() + '.');
                    Bookings bk = new Bookings();
                    bk.setActive(true);
                    bk.setStartTime(startDate);
                    bk.setEndTime(endDate);
                    bk.setDuration((int) (end.getTimeInMillis() - start.getTimeInMillis()) / 1000);
                    bk.setResourcePermission(perm);
                    bk.setResourceType(ResourcePermission.CONSUMER_PERMISSION);
                    bk.setProviderId(request.getBookingID());
                    bk.setUser(user);
                    bk.setUserNamespace(user.getNamespace());
                    bk.setUserName(user.getName());
                    response.setBooking(new BookingsDao(db).persist(bk));

                    /* Notification emails are only sent to home users. */
                    new BookingNotification(bk).notifyCreation();
                } else {
                    this.logger.info("Provider failed to create booking with reason " + request.getReason());

                    /* Provider returned that it couldn't create booking. */
                    for (BookingTime bt : request.getBestFits()) {
                        response.addBestFit(bt.getStart(), bt.getEnd());
                    }
                }
            } else {
                /* Provider call failed. */
                this.logger
                        .info("Provider call to create booking failed with reason " + request.getFailureReason());
                response.setSuccess(false);
                response.setFailureReason("Provider request failed (" + request.getFailureReason() + ")");
            }
        } else {
            BookingCreation bc = this.engine.createBooking(user, perm, new TimePeriod(start, end), db);
            if (bc.wasCreated()) {
                response.setSuccess(true);
                response.setBooking(bc.getBooking());

                /* Notification emails are only sent to home users. */
                if (perm.getRemotePermission() == null)
                    new BookingNotification(bc.getBooking()).notifyCreation();
            } else {
                response.setSuccess(false);
                response.setFailureReason("Resource not free.");

                for (TimePeriod tp : bc.getBestFits()) {
                    numBookings = (Integer) db.createCriteria(Bookings.class).setProjection(Projections.rowCount())
                            .add(Restrictions.eq("active", Boolean.TRUE)).add(Restrictions.eq("user", user))
                            .add(Restrictions.disjunction()
                                    .add(Restrictions.and(Restrictions.gt("startTime", tp.getStartTime().getTime()),
                                            Restrictions.lt("startTime", tp.getEndTime().getTime())))
                                    .add(Restrictions.and(Restrictions.gt("endTime", tp.getStartTime().getTime()),
                                            Restrictions.le("endTime", tp.getEndTime().getTime())))
                                    .add(Restrictions.and(Restrictions.le("startTime", tp.getStartTime().getTime()),
                                            Restrictions.gt("endTime", tp.getEndTime().getTime()))))
                            .uniqueResult();
                    if (numBookings > 0) {
                        this.logger.debug("Excluding best fit option for user " + user.qName() + " because it is "
                                + "concurrent with an existing.");
                        continue;
                    }

                    response.addBestFit(tp.getStartTime(), tp.getEndTime());
                }
            }
        }

        return response;
    }

    @Override
    public BookingOperation cancelBooking(Bookings booking, String reason, boolean user, Session db) {
        BookingOperation status = new BookingOperation();

        if (!booking.isActive()) {
            this.logger.info("Unable to delete booking because the booking has already been canceled or redeemed.");
            status.setFailureReason("Booking already canceled or redeemed.");
            return status;
        }

        if (ResourcePermission.CONSUMER_PERMISSION.equals(booking.getResourcePermission().getType())) {
            CancelBookingRequest multiSite = new CancelBookingRequest();
            if (multiSite.cancelBooking(booking, db) && multiSite.wasSuccessful()) {
                status.setSuccess(true);
                this.logger.debug("Provider successfully cancelled booking with ID '" + booking.getId() + "'.");

                /* Since the provider has cancelled the booking, the local mapped
                 * booked also needs to be cancelled. */
                db.beginTransaction();
                booking.setActive(false);
                booking.setCancelReason(reason);
                db.getTransaction().commit();
            } else {
                String error = multiSite.getFailureReason();
                if (multiSite.getReason() != null)
                    error = multiSite.getReason();
                this.logger.warn("Failed to cancel booking because provider failed with reason: " + error);
                status.setFailureReason(error);
            }
        } else {
            if (!this.engine.cancelBooking(booking, reason, db)) {
                status.setFailureReason("System error.");
            } else {
                status.setSuccess(true);
            }
        }

        /* Fire event that a booking is cancelled. */
        BookingsActivator.notifyBookingsEvent(user ? BookingsEvent.USER_CANCELLED : BookingsEvent.SYSTEM_CANCELLED,
                booking, db);

        return status;
    }
}