package services.beans;
import java.util.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import javax.ejb.*;
import javax.persistence.*;
import services.entities.HotelBooking;
import services.entities.HotelCustomer;
import services.entities.HotelRoom;
/**
* Booking manager bean.
*
* @author Kaspars Zarinovs <k.zarinovs@ncl.ac.uk>
*
*/
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class HotelBookingManagerBean implements HotelBookingManager {
@PersistenceContext(unitName="example")
private EntityManager em;
/**
* Injects HotelCustomerManager dependency
*/
@EJB
private HotelCustomerManager cmb;
/**
* Injects HotelRoomManager dependency
*/
@EJB
private HotelRoomManager rmb;
/**
* @see services.beans.HotelBookingManager#book(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public Long book(String firstName, String lastName, String dateOfBirth, String roomType, String checkInDate, String checkOutDate)
{
Long referenceNumber = null;
HotelRoom room = validateParams(roomType, checkInDate, checkOutDate, firstName, lastName, dateOfBirth);
if(room != null && checkAvailability(room, checkInDate, checkOutDate))
{
referenceNumber = System.currentTimeMillis();
HotelCustomer customer = cmb.createCustomer(firstName, lastName, dateOfBirth);
em.persist(customer);
HotelBooking booking = new HotelBooking(customer, room, referenceNumber, checkInDate, checkOutDate);
em.persist(booking);
}
/* Crash scenario 1. Triggers if firstName = Crash and lastName = Now */
if(firstName.equals("Crash") && lastName.equals("Now"))
Runtime.getRuntime().halt(0);
return referenceNumber;
}
/**
* @see services.beans.HotelBookingManager#cancel(java.lang.Long)
*/
@Override
public boolean cancel(Long referenceNumber)
{
HotelBooking booking = getBookingByReference(referenceNumber);
if(booking == null) return false;
em.remove(booking);
return true;
}
/**
* @see services.beans.HotelBookingManager#getAllRoomTypes()
*/
@Override
public List<String> getAllRoomTypes()
{
return rmb.getAllRoomTypes();
}
/**
* @see services.beans.HotelBookingManager#getBookingByReference(java.lang.Long)
*/
public HotelBooking getBookingByReference(Long referenceNumber)
{
try
{
final String jpaQlQuery = "FROM " + HotelBooking.class.getSimpleName() + " b WHERE b.referenceNumber = :referenceNumber";
Query query = em.createQuery(jpaQlQuery);
query.setParameter("referenceNumber", referenceNumber);
return (HotelBooking) query.getSingleResult();
}
catch(NonUniqueResultException e) { return null; }
catch(NoResultException e) { return null; }
}
/**
* @see services.beans.HotelBookingManager#getRoomByType()
*/
private HotelRoom getRoomByType(String roomType)
{
return rmb.getRoomByType(roomType);
}
/**
* Returns a list of bookings for the specified room type.
*
* @param roomTypeId Long the id of the room in the MySQL database table
* @return List of HotelBookings for the specified room type. Returns <code>null</code> if no bookings exist.
*/
private List<HotelBooking> getBookingsForRoomType(Long roomTypeId)
{
// getResultList() does not throw exceptions if no results are found
// an empty list is enough in this case
final String jpaQlQuery = "FROM " + HotelBooking.class.getSimpleName() + " b WHERE b.room.id = :roomTypeId";
Query query = em.createQuery(jpaQlQuery);
query.setParameter("roomTypeId", roomTypeId);
return query.getResultList();
}
/**
* Validates all parameters. Returns HotelRoom object if successful,
* otherwise returns <code>null</code>
*
* @param roomType String the type of the room
* @param checkInDate String the check-in date of the booking
* @param checkOutDate String the check-out date of the booking
* @param firstName String the custome's first name
* @param lastName String the customer's last name
* @param dateOfBirth String the date of birth of the customer
* @return HotelRoom hotel room, if parameters pass the validation, otherwise returns <code>null</code>
*/
private HotelRoom validateParams(String roomType, String checkInDate, String checkOutDate, String firstName, String lastName, String dateOfBirth)
{
// Check validity of parameters
if(!checkDateFormat(checkInDate)
|| !checkDateFormat(checkOutDate)
|| !checkDateFormat(dateOfBirth)
|| firstName.trim().equals("")
|| lastName.trim().equals(""))
return null;
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
String today = sdf.format(new Date());
HotelRoom room = getRoomByType(roomType);
return (room != null
&& isDateAfter(today, checkOutDate) && isDateAfter(today, checkInDate)
&& isDateAfter(dateOfBirth, today) && isDateAfter(checkOutDate, checkInDate))
? room
: null;
}
/**
* Checks the availability for the booking.
*
* @param room HotelRoom the room of the hotel
* @param checkInDate String the check-in date
* @param checkOutDate String the check-out date
* @return <code>true</code> if succeeds, otherwise returns <code>false</code>
*/
private boolean checkAvailability(HotelRoom room, String checkInDate, String checkOutDate)
{
int availableRooms = room.getAvailableRooms();
List<HotelBooking> bookingsForRoomType = getBookingsForRoomType(room.getId());
int datePeriodUnavailable = 0;
if(bookingsForRoomType == null)
return false;
if(availableRooms > bookingsForRoomType.size())
return true;
for(HotelBooking b : bookingsForRoomType)
{
String bCheckInDate = b.getCheckInDate();
String bCheckOutDate = b.getCheckOutDate();
if(isDateAfter(bCheckOutDate, checkInDate) && isDateAfter(checkOutDate, bCheckInDate))
datePeriodUnavailable++;
}
return availableRooms > datePeriodUnavailable;
}
/**
* Checks if the date is formatted correctly
*
* TODO: Replace with a proper solution that takes into
* account days in a specific month, leap years, etc.
*
* @param dateString String the date string
* @return <code>true</code> if succeeds, otherwise returns <code>false</code>
*/
private boolean checkDateFormat(String dateString)
{
return dateString.matches("(0?[1-9]|[12][0-9]|3[01])-(0?[1-9]|1[012])-((18|19|20|21)\\d\\d)");
}
/**
* Checks whether the first date is after the second date
*
* @param d1String String the first date string
* @param d2String String the second date string
* @return <code>true</code> if succeeds, otherwise returns <code>false</code>
*/
private boolean isDateAfter(String d1String, String d2String)
{
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
Date d1;
Date d2;
try {
d1 = sdf.parse(d1String);
d2 = sdf.parse(d2String);
}
catch(ParseException e) {
return false;
}
return d1.after(d2);
}
}