Class ReservationService

java.lang.Object
de.gustavblass.fsu.fmi.roombooking.service.ReservationService

@Service public class ReservationService extends Object
Provides functionality to manage Reservations, namely to create, modify and delete them.
  • Field Details

  • Constructor Details

    • ReservationService

      public ReservationService(@NonNull @NonNull ReservationRepository reservationRepository, @NonNull @NonNull RoomService roomService, @NonNull @NonNull org.springframework.context.ApplicationEventPublisher eventPublisher)
      Constructs a new ReservationService.
      Parameters:
      reservationRepository - The reservationRepository to be used for database access.
      roomService - Used to get a Room by its Room.number.
      eventPublisher - The eventPublisher.
  • Method Details

    • findById

      @PreAuthorize("hasRole('REGULAR')") @NonNull public @NonNull Optional<Reservation> findById(@NonNull @NonNull Long id)
      Fetches the Reservation with the specified ID from the database.
      Parameters:
      id - The ID to be looked for in the database.
      Returns:
      The Reservation associated with the given ID. Empty if no such Reservation exists.
    • findAll

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull org.springframework.data.domain.Page<Reservation> findAll(@NonNull @NonNull org.springframework.data.domain.Pageable pageable)
      A subset of all Reservations in the database table according to the given Pageable.
      Parameters:
      pageable - The pagination information specifying which page of Reservations shall be returned.
      Returns:
      All Reservations, but only the specified subset.
    • findReservationsByRoom

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull LinkedHashSet<Reservation> findReservationsByRoom(@NonNull @NonNull String roomNumber) throws NotFoundException
      Fetches all Reservations from the database placed for the Room with the given Room.number.
      Parameters:
      roomNumber - The number of the Room whose Reservations shall be looked for.
      Returns:
      All Reservations for the specified Room.
      Throws:
      NotFoundException - If the given room number does not exist.
    • findReservationsByApplicant

      @PreAuthorize("#applicant == authentication.principal or hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull LinkedHashSet<Reservation> findReservationsByApplicant(@NonNull @NonNull Actor applicant)
      Fetches all Reservations from the database placed by the given Actor.
      Parameters:
      applicant - The user whose Reservations shall be looked for.
      Returns:
      All Reservations of the specified user.
    • findCurrentReservationsByApplicant

      @PreAuthorize("#applicant == authentication.principal or hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull org.springframework.data.domain.Page<Reservation> findCurrentReservationsByApplicant(@NonNull @NonNull Actor applicant, @NonNull @NonNull org.springframework.data.domain.Pageable pageable)
      Fetches all Reservations – which end after now – from the database placed by the given Actor.
      Parameters:
      applicant - The user whose Reservations shall be looked for.
      pageable - The pagination information specifying which page of Reservations shall be returned.
      Returns:
      All Reservations of the specified user.
    • findReservationsByApplicant

      @PreAuthorize("#userName == authentication.principal.getUsername() or hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull org.springframework.data.domain.Page<Reservation> findReservationsByApplicant(@NonNull @NonNull String userName, @NonNull @NonNull org.springframework.data.domain.Pageable pageable)
      Fetches all Reservations from the database placed by the Actor with the given Actor.userName.
      Parameters:
      userName - The user whose Reservations shall be looked for.
      pageable - The pagination information specifying which page of Reservations shall be returned.
      Returns:
      All Reservations of the specified user.
    • isRoomFree

      public boolean isRoomFree(@NonNull @NonNull Room room)
      Checks whether the given Room is currently free for use or occupied by a Reservation.
      Parameters:
      room - The Room whose status shall be checked.
      Returns:
      True if no Reservation is currently active for the Room.
    • findReservationsByDateTime

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull Set<Reservation> findReservationsByDateTime(@NonNull @NonNull String roomNumber, @NonNull @NonNull LocalDateTime startDate, @NonNull @NonNull LocalDateTime endDate) throws de.gustavblass.commons.exceptions.IllegalArgumentException, NotFoundException

      Fetches all Reservations from the database placed for the Room with the given Room.id and starting or ending between the two given dates.

      To be clear: Only those Reservations are returned that simultaneously match the Room ID and the specified date interval.

      Parameters:
      roomNumber - The number of the Room whose Reservations shall be looked for.
      startDate - The Reservations looked for start or end after this date. The start date must come before the end date.
      endDate - The Reservations looked for start or end before this date. The end date must come after the start date.
      Returns:
      The Reservations between the two dates for the given Room.
      Throws:
      NotFoundException - If the given room number does not exist.
      de.gustavblass.commons.exceptions.IllegalArgumentException - If the end date is not after the start date.
    • findReservationsByDateTime

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull Set<Reservation> findReservationsByDateTime(@NonNull @NonNull LocalDateTime startDate, @NonNull @NonNull LocalDateTime endDate) throws de.gustavblass.commons.exceptions.IllegalArgumentException
      Fetches all Reservations from the database that start or end between the two given dates.
      Parameters:
      startDate - The Reservations looked for start or end after this date. The start date must come before the end date.
      endDate - The Reservations looked for start or end before this date. The end date must come after the start date.
      Returns:
      All Reservations between the two dates.
      Throws:
      de.gustavblass.commons.exceptions.IllegalArgumentException - If the end date is not after the start date.
    • findReservationsAfterDateTime

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") @NonNull public @NonNull LinkedHashSet<Reservation> findReservationsAfterDateTime(@NonNull @NonNull Actor applicant, @NonNull @NonNull LocalDateTime date)

      Fetches all Reservations from the database placed for the Reservation.applicant with the given Actor.id and starting or ending after the given date.

      To be clear: Only those Reservations are returned that simultaneously match the Actor ID and the specified lower (exclusive) date-boundary.

      Parameters:
      applicant - The Actor whose Reservations shall be looked for.
      date - The Reservations looked for start or end after this date (greater than).
      Returns:
      The Reservations between the two dates for the given Room.
    • isSeatReservationPossible

      public boolean isSeatReservationPossible(@NonNull @NonNull Room room, @NonNull @NonNull LocalDate date, @NonNull @NonNull LocalTime startTime, @NonNull @NonNull LocalTime endTime, int seatCount) throws de.gustavblass.commons.exceptions.IllegalArgumentException, TimeTravelException, ReversedTimePeriodException
      Checks whether the given number of seats is available for use in the given Room during the entire duration between the startTime and endTime on the given date.
      Parameters:
      room - The Room whose seat availability shall be checked.
      date - The LocalDate for which to check the seat availability. Must not come before today
      startTime - The seat availability shall be checked only for the period after this LocalTime on the date. Must not come before LocalTime.now().
      endTime - The seat availability shall be checked only for the period before this LocalTime on the date. Must come after the startTime.
      seatCount - The number of seats that shall be available in the Room. Must be greater than zero and not greater than the Room.capacity of the given Room.
      Returns:
      True if it is possible to place a new SeatReservation with the specified SeatReservation.count is possible for the given time period in the given Room.
      Throws:
      TimeTravelException - If the time period is in the past.
      ReversedTimePeriodException - If the given start time does not come before the given end time.
      de.gustavblass.commons.exceptions.IllegalArgumentException - If the seat count is less than 1 or if the seat count exceeds the given Room's capacity.
    • buildSchedule

      @NonNull public @NonNull RoomSchedule buildSchedule(@NonNull @NonNull Room room, @NonNull @NonNull LocalDate date)

      Alias for buildSchedule(Room, LocalDate, LocalTime, LocalTime) with LocalTime.MIN as the startTime and LocalTime.MAX as the endTime.

      Creates a RoomSchedule for the given Room on the given LocalDate.

      Parameters:
      room - The Room for which the schedule shall be built.
      date - The day for which the schedule shall be valid.
      Returns:
      The schedule for the given Room on the given day.
    • buildSchedule

      @NonNull public @NonNull RoomSchedule buildSchedule(@NonNull @NonNull Room room, @NonNull @NonNull LocalDate date, @NonNull @NonNull MergeOptions mergeOptions)
    • buildSchedule

      @NonNull public @NonNull RoomSchedule buildSchedule(@NonNull @NonNull Room room, @NonNull @NonNull LocalDate date, @NonNull @NonNull LocalTime startTime, @NonNull @NonNull LocalTime endTime) throws ReversedTimePeriodException, ReservationPeriodTooSmallException
      Throws:
      ReversedTimePeriodException
      ReservationPeriodTooSmallException
    • buildSchedule

      @NonNull public @NonNull RoomSchedule buildSchedule(@NonNull @NonNull Room room, @NonNull @NonNull LocalDate date, @NonNull @NonNull LocalTime startTime, @NonNull @NonNull LocalTime endTime, @NonNull @NonNull MergeOptions mergeOptions) throws ReversedTimePeriodException, ReservationPeriodTooSmallException

      Creates a RoomSchedule for the given Room on the given LocalDate between the given startTime and endTime.

      The schedule will include

      • one RoomScheduleItem for each Reservation matching the given parameters;
      • one schedule item for each time period – between the start and end time – during which there is no Reservation, this includes the period before the first reservation and the period after the last reservation.

      If there is no Reservation for the Room between the start and end time, the whole period will be a free period.

      Parameters:
      room - The Room for which the schedule shall be built.
      date - The day for which the schedule shall be valid.
      startTime - Ignore Reservations that come before this point in time. Must come >5 minutes before the endTime!
      endTime - Ignore Reservations that come after this point in time. Must come >5 minutes after the startTime!
      mergeOptions - The resulting schedule items will be merged according to these options.
      Returns:
      The schedule for the given Room between the given startTime and endTime on the given day.
      Throws:
      ReversedTimePeriodException - If the start time does not come before the end time.
      ReservationPeriodTooSmallException - If the startTime does not come >5 minutes before the endTime.
      Implementation Note:
      Please put on protective goggles before looking at this code!
    • valideTimeInterval

      @Contract(pure=true) private void valideTimeInterval(@NonNull @NonNull LocalDateTime startDate, @NonNull @NonNull LocalDateTime endDate) throws de.gustavblass.commons.exceptions.IllegalArgumentException

      Checks that:

      Parameters:
      startDate - The date that is supposed to come first.
      endDate - The date that is supposed to come second.
      Throws:
      de.gustavblass.commons.exceptions.IllegalArgumentException - If the validation failed.
    • bookRoom

      @PreAuthorize("hasRole('REGULAR')") @NonNull public @NonNull Reservation bookRoom(@NonNull @NonNull BookRoomDTO reservationDto) throws NotFoundException, IllegalStateException, TimeTravelException, ReversedTimePeriodException, ReservationPeriodTooSmallException, ReservationPeriodTooBigException, InvalidUseException, ConflictingReservationsException, InvalidSeatCountException, de.gustavblass.commons.exceptions.IllegalArgumentException

      Saves a new Reservation to the database, based on the given BookRoomDTO, if the FilterReservationsDTO.room is free between the start time and end time.

      Publishes a ReservationConfirmedEvent with the new Reservation.

      Parameters:
      reservationDto - The Reservation that shall be placed, represented as a data-transfer object.
      Returns:
      The Reservation created from the DTO.
      Throws:
      NotFoundException - If the Room.number does not exist.
      TimeTravelException - If the reservation starts in the past.
      ReversedTimePeriodException - If FilterReservationsDTO.from does not come before FilterReservationsDTO.to.
      ReservationPeriodTooSmallException - If FilterReservationsDTO.to comes too shortly after FilterReservationsDTO.from.
      ReservationPeriodTooBigException - If the period between FilterReservationsDTO.from and FilterReservationsDTO.to is too long.
      ConflictingReservationsException - If there is already at least one Reservation for the Room in the specified time period.
      InvalidUseException - If the BookRoomDTO.use is not an IntendedUse enum value.
      InvalidSeatCountException - If the BookRoomDTO.type is ReservationType.SEAT_RESERVATION, but no seat count greater than zero is specified.
      de.gustavblass.commons.exceptions.IllegalArgumentException - If the Room, FilterReservationsDTO.date, start time, end time, type or intended use is not specified.
      IllegalStateException - If the logged-in user cannot be found.
    • exists

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") public boolean exists(@NonNull @NonNull Long id)
      Checks whether the database contains a Reservation with the given Reservation.id.
      Parameters:
      id - The Reservation identifier that shall be tested.
      Returns:
      True if the ID exists, false if there is no such Reservation.
    • delete

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") public void delete(@NonNull @NonNull Long id) throws NotFoundException
      Permanently removes the Reservation with the given Reservation.id from the database.
      Parameters:
      id - The identifier of the Reservation that shall no longer be stored in the system.
      Throws:
      NotFoundException - If no Reservation with the given ID exists.
    • deleteOwnReservation

      @PreAuthorize("hasRole('REGULAR')") public void deleteOwnReservation(@NonNull @NonNull Long id) throws NotFoundException
      Permanently removes the current Actor's Reservation with the given Reservation.id from the database.
      Parameters:
      id - The identifier of the Reservation that shall no longer be stored in the system.
      Throws:
      NotFoundException - If no Reservation with the given ID exists.
      Implementation Note:
      This is a separate method from delete(Long), because otherwise a malicious user would be able to tell if the Reservation ID exists, even if the Reservation belongs to a different applicant. This timing attack would be facilitated by the fact that it takes slightly longer to check the user's authorisation than to check the ID's existence. (The authorisation can only be checked if the ID exists.)
    • deleteOwnReservations

      public void deleteOwnReservations(@NonNull @NonNull Iterable<Long> ids) throws NotFoundException
      Permanently removes the Reservations which belong to the current Actor and have the given identifiers from the database, if all of them exist.
      Parameters:
      ids - The identifiers of the Reservations that shall no longer be stored in the system.
      Throws:
      NotFoundException - If at least for one of the given IDs, there is no corresponding Reservation in the database. Then, no Reservation is deleted.
    • delete

      @PreAuthorize("hasRole('FACULTY_ADMINISTRATION')") public void delete(@NonNull @NonNull Iterable<Long> ids) throws NotFoundException
      Permanently removes the Reservations with the given identifiers from the database, if all of them exist.
      Parameters:
      ids - The identifiers of the Reservations that shall no longer be stored in the system.
      Throws:
      NotFoundException - If at least for one of the given IDs, there is no corresponding Reservation in the database. Then, no Reservation is deleted.
    • computeMaximumOccupancy

      @Contract(pure=true) @NonNull public static @NonNull Integer computeMaximumOccupancy(@NonNull @NonNull Collection<SeatReservation> reservations)
      Calculates the highest amount of overlapping SeatReservations among the given ones.
      Parameters:
      reservations - The SeatReservations among which the maximum occupancy shall be calculated.
      Returns:
      The maximum number of simultaneously occupied seats.
    • computeMaximumOccupancy

      @Contract(pure=true) @NonNull public static @NonNull Optional<Integer> computeMaximumOccupancy(@NonNull @NonNull Collection<SeatReservation> reservations, @NonNull @NonNull Integer threshold)

      Calculates the highest amount of overlapping SeatReservations among the given ones.

      This method will return early if the preliminary result exceeds this given threshold. This may improve the performance, depending on the number of given SeatReservations.

      Parameters:
      reservations - The SeatReservations among which the maximum occupancy shall be calculated.
      threshold - The maximum occupancy tolerated before the computation shall be cancelled.
      Returns:
      The maximum number of simultaneously occupied seats. Empty if the number is greater than the given threshold.
    • convertSeatReservationChainToSchedule

      @Contract(pure=true) @NonNull public static @NonNull @Unmodifiable List<RoomScheduleItem> convertSeatReservationChainToSchedule(@NonNull @NonNull Collection<SeatReservation> chain, int capacity)
    • convertSeatReservationChainToSchedule

      @Contract(pure=true) @NonNull public static @NonNull @Unmodifiable List<RoomScheduleItem> convertSeatReservationChainToSchedule(@NonNull @NonNull Collection<SeatReservation> chain, int capacity, @NonNull @NonNull MergeOptions mergeOptions)

      Creates a new list of RoomScheduleItems corresponding to the given chain of SeatReservations.

      If the SeatReservations overlap, then there will be a separate RoomScheduleItem for each overlapping time period, with the RoomScheduleItem.occupancy computed accordingly.

      If two consecutive RoomScheduleItems have the same occupancy, they will be merged into one single RoomScheduleItem. (Consecutive means that the first item's RoomScheduleItem.endTime is equal to the second item's RoomScheduleItem.startTime.)

      For RoomScheduleItems with RoomStatus.FREE, the RoomScheduleItem.occupancy will be null.

      Parameters:
      chain - The SeatReservations that shall be converted to a schedule.
      capacity - The Room.capacity of the Room for which the schedule of the given SeatReservations shall be built. This number is used to determine whether the Room is RoomStatus.PARTIALLY_OCCUPIED or RoomStatus.OCCUPIED. If the Room shall never be fully occupied, the capacity parameter shall be set to Integer.MAX_VALUE.
      mergeOptions - The resulting schedule items will be merged according to these options.
      Returns:
      The schedule built from the given SeatReservations. Empty if no SeatReservations were given.
      Implementation Note:
      This method has a time complexity of O(n log n) where n is the Collection.size().
    • convertToTimespanOverlaps

      @Contract(pure=true) @NonNull public static @NonNull List<TimespanOverlapItem> convertToTimespanOverlaps(@NonNull @NonNull Collection<SeatReservation> reservations)

      Turns the given SeatReservations into TimespanOverlapItems, sorted by the TimespanOverlapItem.time.

      For each SeatReservation, there will be one TimespanOverlapItem whose TimespanOverlapItem.type is TimespanOverlapItem.Type.START and one TimespanOverlapItem whose type is TimespanOverlapItem.Type.END. On both TimespanOverlapItems, the TimespanOverlapItem.value will be set to the SeatReservation.count.

      Parameters:
      reservations - The SeatReservations that shall be converted.
      Returns:
      The corresponding TimespanOverlapItems. Twice as many as SeatReservations.
      Implementation Note:
      As a safety precaution, only seat counts greater than zero will be used (otherwise: one). This method has a time complexity of O(n log n) where n is the Collection.size().
    • merge

      @Contract(pure=true) @NonNull public static @NonNull @Unmodifiable List<RoomScheduleItem> merge(@NonNull @NonNull List<RoomScheduleItem> schedule, int roomCapacity, @NonNull @NonNull MergeOptions mergeOptions)
      Merges consecutive RoomScheduleItems in the given schedule according to the given MergeOptions.
      Parameters:
      schedule - The RoomScheduleItems that shall be merged according to the given options.
      roomCapacity - The Room.capacity of the Room which the Reservations represented by the given RoomScheduleItems correspond to. (Necessary to compute a useful merge score.)
      mergeOptions - Define the behaviour of this method.
      Returns:
      A new unmodifiable schedule where a certain number of items has been merged. The time order of the given items is preserved, of course.
      Implementation Note:
      This method has a time complexity of O(n) where n is the size of the given schedule.
    • merge

      @Contract(pure=true) @NonNull private static @NonNull RoomScheduleItem merge(@NonNull @NonNull RoomScheduleItem item1, @NonNull @NonNull RoomScheduleItem item2)

      Creates a new RoomScheduleItem from the two given ones.

      The RoomScheduleItem.startTime will be the start time of item1, while the RoomScheduleItem.endTime will be the end time of item2.

      The RoomScheduleItem.status will be the highest one between item1's and item2's statuses.

      The RoomScheduleItem.occupancy will be max{item1.occupancy, item2.occupancy}.

      Parameters:
      item1 - The RoomScheduleItem that comes first in time.
      item2 - The RoomScheduleItem that comes second in time.
      Returns:
      The merging result.
      Implementation Note:
      This method has constant time complexity.