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.