5

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.