Conversations
The hotel booking "wizard" is implemented by a conversation-scoped
stateful session bean. All Seam components are in the
conversation scope by default.
The HotelBookingAction
maintains
state associated with the booking process in the Seam conversation
scope. This ensures that if the user is working in multiple brower
tabs or multiple brower windows, the various conversations are
completely isolated from each other.
To see this working in practice, right click on the "View Hotel" button in the search screen and select "open in new tab" or "open in new window", and try working on multiple hotel bookings simultaneously. In the next step, we will discuss Seam's built-in components to manage multiple concurrent conversations.
@Stateful
@Name("hotelBooking")
@LoggedIn
public class HotelBookingAction implements HotelBooking
{
@PersistenceContext(type=EXTENDED)
private EntityManager em;
@In
private User user;
@In(required=false) @Out
private Hotel hotel;
@In(required=false)
@Out(required=false)
private Booking booking;
@In(create=true)
private FacesMessages facesMessages;
@In(create=true)
private Events events;
@In
private HotelSearching hotelSearch;
@Logger
private Log log;
private boolean bookingValid;
@Begin
public void selectHotel(Hotel selectedHotel)
{
hotel = em.merge(selectedHotel);
}
public void bookHotel()
{
booking = new Booking(hotel, user);
Calendar calendar = Calendar.getInstance();
booking.setCheckinDate( calendar.getTime() );
calendar.add(Calendar.DAY_OF_MONTH, 1);
booking.setCheckoutDate( calendar.getTime() );
}
public void setBookingDetails()
{
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -1);
if ( booking.getCheckinDate().before( calendar.getTime() ) )
{
facesMessages.addToControl("checkinDate", "Check in date must be a future date");
bookingValid=false;
}
else if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
{
facesMessages.addToControl("checkoutDate", "Check out date must be later than check in date");
bookingValid=false;
}
else
{
bookingValid=true;
}
}
public boolean isBookingValid()
{
return bookingValid;
}
@End
public void confirm()
{
em.persist(booking);
facesMessages.add("Thank you, #{user.name}, your confimation number for #{hotel.name} is #{booking.id}");
log.info("New booking: #{booking.id} for #{user.username}");
events.raiseTransactionSuccessEvent("bookingConfirmed");
}
@End
public void cancel() {}
@Remove
public void destroy() {}
}
The conversation begins when the @Begin
annotated
selectHotel()
is called, and ends when
the @End
annotated
confirm()
or cancel()
is called. Between the
@Begin
and @End
methods, the user can do
any number of things with the application (i.e., invoke any
event handler method or use the BACK button etc.) and the
hotelBooking
maintains its state throughout the process.
When the @End
method is called, Seam destroys this
component and avoids any memory leak.
However, none of the HotelBookingAction
bean methods
may be called outside of a long-running conversation.
So if we try to use the
back button after the end of the conversation, Seam will redirect to the main page, with an
error message.