[seam-commits] Seam SVN: r9290 - in trunk/doc/Seam_Reference_Guide/en-US: images and 1 other directory.
seam-commits at lists.jboss.org
seam-commits at lists.jboss.org
Mon Oct 13 22:06:02 EDT 2008
Author: jacob.orshalick
Date: 2008-10-13 22:06:01 -0400 (Mon, 13 Oct 2008)
New Revision: 9290
Added:
trunk/doc/Seam_Reference_Guide/en-US/images/nested-booking.png
Modified:
trunk/doc/Seam_Reference_Guide/en-US/Tutorial.xml
Log:
JBSEAM-2295
Modified: trunk/doc/Seam_Reference_Guide/en-US/Tutorial.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Tutorial.xml 2008-10-14 02:02:05 UTC (rev 9289)
+++ trunk/doc/Seam_Reference_Guide/en-US/Tutorial.xml 2008-10-14 02:06:01 UTC (rev 9290)
@@ -2505,7 +2505,7 @@
<section>
<title>The Seam Debug Page</title>
- <para> The WAR also includes <literal>seam-debug.jar</literal>. The Seam debug page will be availabled
+ <para> The WAR also includes <literal>seam-debug.jar</literal>. The Seam debug page will be available
if this jar is deployed in
<literal>WEB-INF/lib</literal>, along with the Facelets, and if you set the debug property
of the <literal>init</literal> component:</para>
@@ -2530,7 +2530,391 @@
</section>
</section>
-
+
+ <section id="nestedbooking">
+ <title>Nested conversations: extending the Hotel Booking example</title>
+
+ <section>
+ <title>Introduction</title>
+
+ <para>Long-running conversations make it simple to maintain consistency of state in an application
+even in the face of multi-window operation and back-buttoning. Unfortunately, simply beginning and ending a
+long-running conversation is not always enough. Depending on the requirements of the application, inconsistencies
+between what the user's expectations and the reality of the application’s state can still result.</para>
+
+ <para>The nested booking application extends the features of the hotel booking application to incorporate
+the selection of rooms. Each hotel has available rooms with descriptions for a user to select from. This requires
+the addition of a room selection page in the hotel reservation flow.</para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/nested-booking.png" align="center" scalefit="1"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/nested-booking.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>The user now has the option to select any available room to be included in the booking. As with the
+hotel booking application we saw previously, this can lead to issues with state consistency. As with storing state
+in the <varname>HTTPSession</varname>, if a conversation variable changes it affects all windows operating within
+the same conversation context.</para>
+
+ <para>To demonstrate this, let’s suppose the user clones the room selection screen in a new window. The
+user then selects the <emphasis>Wonderful Room</emphasis> and proceeds to the confirmation screen. To see just how much
+it would cost to live the high-life, the user returns to the original window, selects the <emphasis>Fantastic
+Suite</emphasis> for booking, and again proceeds to confirmation. After reviewing the total cost, the user decides
+that practicality wins out and returns to the window showing <emphasis>Wonderful Room</emphasis> to confirm.</para>
+
+ <para>In this scenario, if we simply store all state in the conversation, we are not protected from
+multi-window operation within the same conversation. Nested conversations allow us to achieve correct behavior even
+when context can vary within the same conversation.</para>
+ </section>
+
+ <section>
+ <title>Understanding Nested Conversations</title>
+
+ <para>Now let's see how the nested booking example extends the behavior of the hotel booking application through
+use of nested conversations. Again, we can read the class from top to bottom, as if it were a story.</para>
+
+ <example>
+ <title>RoomPreferenceAction.java</title>
+ <!-- Can't use code hightlighting with callouts -->
+ <programlistingco>
+ <areaspec>
+ <area id="nested-booking-load-rooms" coords="25"/>
+ <area id="nested-booking-nested-conversation" coords="38"/>
+ <area id="nested-booking-select-preference" coords="43"/>
+ <area id="nested-booking-end-annotation" coords="58"/>
+ </areaspec>
+ <programlisting><![CDATA[@Stateful
+ at Name("roomPreference")
+ at Restrict("#{identity.loggedIn}")
+public class RoomPreferenceAction implements RoomPreference
+{
+
+ @Logger
+ private Log log;
+
+ @In private Hotel hotel;
+
+ @In private Booking booking;
+
+ @DataModel(value="availableRooms")
+ private List<Room> availableRooms;
+
+ @DataModelSelection(value="availableRooms")
+ private Room roomSelection;
+
+ @In(required=false, value="roomSelection")
+ @Out(required=false, value="roomSelection")
+ private Room room;
+
+ @Factory("availableRooms")
+ public void loadAvailableRooms()
+ {
+ availableRooms = hotel.getAvailableRooms(booking.getCheckinDate(), booking.getCheckoutDate());
+ log.info("Retrieved #0 available rooms", availableRooms.size());
+ }
+
+ public BigDecimal getExpectedPrice()
+ {
+ log.info("Retrieving price for room #0", roomSelection.getName());
+
+ return booking.getTotal(roomSelection);
+ }
+
+ @Begin(nested=true)
+ public String selectPreference()
+ {
+ log.info("Room selected");
+
+ this.room = this.roomSelection;
+
+ return "payment";
+ }
+
+ public String requestConfirmation()
+ {
+ // all validations are performed through the s:validateAll, so checks are already
+ // performed
+ log.info("Request confirmation from user");
+
+ return "confirm";
+ }
+
+ @End(beforeRedirect=true)
+ public String cancel()
+ {
+ log.info("ending conversation");
+
+ return "cancel";
+ }
+
+ @Destroy @Remove
+ public void destroy() {}
+}
+]]></programlisting>
+ <calloutlist>
+ <callout arearefs="nested-booking-load-rooms">
+ <para> The <varname>hotel</varname> instance is injected from the conversation context. The hotel
+ is loaded through an <emphasis>extended persistence context</emphasis> so that the entity
+ remains managed throughout the conversation. This allows us to lazily load the
+ <varname>availableRooms</varname> through an <varname>@Factory</varname> method by
+ simply walking the assocation.
+ </para>
+ </callout>
+ <callout arearefs="nested-booking-nested-conversation">
+ <para> When <link linkend="begin-annotation">
+ <literal>@Begin(nested=true)</literal>
+ </link> is encountered, a nested conversation is pushed onto the conversation stack. When
+ executing within a nested conversation, components still have access to all outer conversation
+ state, but setting any values in the nested conversation’s state container does not affect
+ the outer conversation. In addition, nested conversations can exist concurrently stacked on the
+ same outer conversation, allowing independent state for each.</para>
+ </callout>
+ <callout arearefs="nested-booking-select-preference">
+ <para>The <varname>roomSelection</varname> is outjected to the conversation based on the
+ <varname>@DataModelSelection</varname>. Note that because the nested conversation has an
+ independent context, the <varname>roomSelection</varname> is only set into the new nested
+ conversation. Should the user select a different preference in another window or tab a new
+ nested conversation would be started.</para>
+ </callout>
+ <callout arearefs="nested-booking-end-annotation">
+ <para> The <link linkend="end-annotation">
+ <literal>@End</literal>
+ </link> annotation pops the conversation stack and resumes the outer conversation. The
+ <varname>roomSelection</varname> is destroyed along with the conversation context.</para>
+ </callout>
+ </calloutlist>
+ </programlistingco>
+ </example>
+
+ <para>When we being a nested conversation it is pushed onto the conversation stack. In the <varname>nestedbooking</varname>
+example, the conversation stack consists of the outer long-running conversation (the booking) and each of the nested conversations (room
+selections).</para>
+
+ <example>
+ <title>rooms.xhtml</title>
+ <!-- Can't use code hightlighting with callouts -->
+ <programlistingco>
+ <areaspec>
+ <area id="nested-booking-available-rooms" coords="19"/>
+ <area id="nested-booking-selection-action" coords="36"/>
+ <area id="nested-booking-cancel-action" coords="45"/>
+ </areaspec>
+ <programlisting><![CDATA[<div class="section">
+ <h1>Room Preference</h1>
+</div>
+
+<div class="section">
+ <h:form id="room_selections_form">
+ <div class="section">
+ <h:outputText styleClass="output"
+ value="No rooms available for the dates selected: "
+ rendered="#{availableRooms != null and availableRooms.rowCount == 0}"/>
+ <h:outputText styleClass="output"
+ value="Rooms available for the dates selected: "
+ rendered="#{availableRooms != null and availableRooms.rowCount > 0}"/>
+
+ <h:outputText styleClass="output" value="#{booking.checkinDate}"/> -
+ <h:outputText styleClass="output" value="#{booking.checkoutDate}"/>
+
+ <br/><br/>
+
+ <h:dataTable value="#{availableRooms}" var="room"
+ rendered="#{availableRooms.rowCount > 0}">
+ <h:column>
+ <f:facet name="header">Name</f:facet>
+ #{room.name}
+ </h:column>
+ <h:column>
+ <f:facet name="header">Description</f:facet>
+ #{room.description}
+ </h:column>
+ <h:column>
+ <f:facet name="header">Per Night</f:facet>
+ <h:outputText value="#{room.price}">
+ <f:convertNumber type="currency" currencySymbol="$"/>
+ </h:outputText>
+ </h:column>
+ <h:column>
+ <f:facet name="header">Action</f:facet>
+ <h:commandLink id="selectRoomPreference"
+ action="#{roomPreference.selectPreference}">Select</h:commandLink>
+ </h:column>
+ </h:dataTable>
+ </div>
+ <div class="entry">
+ <div class="label"> </div>
+ <div class="input">
+ <s:button id="cancel" value="Revise Dates" view="/book.xhtml"/>
+ </div>
+ </div>
+ </h:form>
+</div>
+]]></programlisting>
+ <calloutlist>
+ <callout arearefs="nested-booking-available-rooms">
+ <para>When requested from EL, the <varname>#{availableRooms}</varname> are loaded by the <varname>@Factory</varname>
+method defined in <varname>RoomPreferenceAction</varname>. The <varname>@Factory</varname> method will only be executed once to load the values
+into the current context as a <link linkend="datamodel-annotation">
+ <varname>@DataModel</varname>
+</link> instance.</para>
+ </callout>
+ <callout arearefs="nested-booking-selection-action">
+ <para>Invoking the <varname>#{roomPreference.selectPreference}</varname> action results in the row being selected
+and set into the <varname>@DataModelSelection</varname>. This value is then outjected to the nested conversation context.</para>
+ </callout>
+ <callout arearefs="nested-booking-cancel-action">
+ <para>Revising the dates simply returns to the <varname>/book.xhtml</varname>. Note that we have not yet nested
+a conversation (no room preference has been selected), so the current conversation can simply be resumed. The <varname><s:button></varname>
+component simply propagates the current conversation when displaying the <varname>/book.xhtml</varname> view.</para>
+ </callout>
+ </calloutlist>
+ </programlistingco>
+ </example>
+
+ <para>Now that we have seen how to nest a conversation, let's see how we can confirm the booking once a room has been selected. This
+can be achieved by simply extending the behavior of the <varname>HotelBookingAction</varname>.</para>
+
+ <example>
+ <title>HotelBookingAction.java</title>
+ <!-- Can't use code hightlighting with callouts -->
+ <programlistingco>
+ <areaspec>
+ <area id="nested-booking-end-root" coords="77"/>
+ <area id="nested-booking-confirm" coords="82"/>
+ <area id="nested-booking-cancel" coords="89" />
+ </areaspec>
+ <programlisting><![CDATA[@Stateful
+ at Name("hotelBooking")
+ at Restrict("#{identity.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(required=false)
+ private Room roomSelection;
+
+ @In
+ private FacesMessages facesMessages;
+
+ @In
+ private Events events;
+
+ @Logger
+ private Log log;
+
+ @Begin
+ public void selectHotel(Hotel selectedHotel)
+ {
+ log.info("Selected hotel #0", selectedHotel.getName());
+ hotel = em.merge(selectedHotel);
+ }
+
+ public String setBookingDates()
+ {
+ // the result will indicate whether or not to begin the nested conversation
+ // as well as the navigation. if a null result is returned, the nested
+ // conversation will not begin, and the user will be returned to the current
+ // page to fix validation issues
+ String result = null;
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(Calendar.DAY_OF_MONTH, -1);
+
+ // validate what we have received from the user so far
+ if ( booking.getCheckinDate().before( calendar.getTime() ) )
+ {
+ facesMessages.addToControl("checkinDate", "Check in date must be a future date");
+ }
+ else if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
+ {
+ facesMessages.addToControl("checkoutDate", "Check out date must be later than check in date");
+ }
+ else
+ {
+ result = "rooms";
+ }
+
+ return result;
+ }
+
+ 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() );
+ }
+
+ @End(root=true)
+ public void confirm()
+ {
+ // on confirmation we set the room preference in the booking. the room preference
+ // will be injected based on the nested conversation we are in.
+ booking.setRoomPreference(roomSelection);
+
+ 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(root=true, beforeRedirect=true)
+ public void cancel() {}
+
+ @Destroy @Remove
+ public void destroy() {}
+}
+]]></programlisting>
+ <calloutlist>
+ <callout arearefs="nested-booking-end-root">
+ <para>Annotating an action with <link linkend="end-annotation">
+ <varname>@End(root=true)</varname>
+ </link> ends the root conversation which effectively destroys the entire conversation stack.
+ When any conversation is ended, it's nested conversations are ended as well. As the root is
+ the conversation that started it all, this is a simple way to destroy and release all state
+ associated with a workspace once the booking is confirmed.</para>
+ </callout>
+ <callout arearefs="nested-booking-confirm">
+ <para>The <varname>roomSelection</varname> is only associated with the <varname>booking</varname>
+ on user confirmation. While outjecting values to the nested conversation context will not
+ impact the outer conversation, any objects injected from the outer conversation are injected
+ by reference. This means that any changing to these objects will be reflected in the parent
+ conversation as well as other concurrent nested conversations.</para>
+ </callout>
+ <callout arearefs="nested-booking-cancel">
+ <para>By simply annotating the cancellation action with <link linkend="end-annotation">
+ <varname>@End(root=true, beforeRedirect=true)</varname>
+ </link>
+ we can easily destroy and release all state associated with the
+ workspace prior to redirecting the user back to the hotel selection view.</para>
+ </callout>
+ </calloutlist>
+ </programlistingco>
+ </example>
+
+ <para>Feel free to deploy the application, open many windows or tabs and attempt combinations of various hotels with
+various room preferences. Confirming a booking always results in the correct hotel and room preference thanks to the nested
+conversation model.</para>
+ </section>
+ </section>
+
<section id="dvdstore">
<title>A complete application featuring Seam and jBPM: the DVD Store example</title>
Added: trunk/doc/Seam_Reference_Guide/en-US/images/nested-booking.png
===================================================================
(Binary files differ)
Property changes on: trunk/doc/Seam_Reference_Guide/en-US/images/nested-booking.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
More information about the seam-commits
mailing list