1.7.2. ネストされた対話の理解
次のコードでは、ネストされた対話の拡張された動作によるホテル予約アプリケーションの動作を示しています。繰り返しになりますが、コードを順を追って読むステップの 1 セットとして考えると理解できます。
例1.31 RoomPreferenceAction.java
@Stateful
@Name("roomPreference")
@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() {}
}
| hotel インスタンスは対話コンテキストからインジェクトされます。ホテルは 拡張永続コンテキスト により読み込まれるため、エンティティは対話全体を通じて管理されたままとなります。これにより、単に関連付けることで、@Factory メソッドから availableRooms を遅延して読み込むことができます。
|
| @Begin(nested=true) が出現すると、 ネストされた対話は対話スタックにプッシュされます。 ネストされた対話中で実行する場合、 コンポーネントはまだ外側の対話状態すべてにアクセスできますが、 ネストされた対話の状態コンテナに値を設定しても対話の外側には影響しません。 また、 ネストされた対話は同じ外側の対話に対して同時並行的にスタックして存在することが可能で、それぞれの状態は独立しています。
|
| roomSelection は @DataModelSelection に基づいた対話にアウトジェクトされます。ネストされた対話は独立したコンテキストを持つので、roomSelection は新しいネストされた対話にのみ設定されることに留意してください。 ユーザーが別のウインドウまたはタブで異なる選択をした場合、新しいネストされた対話が開始されます。
|
| @End アノテーションは対話スタックをポップしてから外側の対話を再開します。roomSelection は対話コンテキストと共に破棄されます。
|
ネストした対話を開始すると対話スタックにプッシュされます。
nestedbooking サンプルでは、 対話スタックは外部の長期実行の対話 (予約) とネストした各対話 (部屋選択) から構成されます。
例1.32 rooms.xhtml
<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>
|
EL から求められると、 RoomPreferenceAction に定義された @Factory メソッドにより #{availableRooms} がロードされます。@Factory メソッドは、@DataModel インスタンスとして現在のコンテキストに値をロードするために 1 度だけ実行されます。
|
| #{roomPreference.selectPreference} アクションを呼び出すことにより、行が選択され @DataModelSelection に値が設定されます。 そして値はネストされた対話コンテキストにアウトジェクトされます。
|
|
日付を変更すると単純に /book.xhtml に戻されます。 まだ対話をネストしていないため (選択された部屋がない)、 現在の対話は単に再開可能であることに留意してください。<s:button> コンポーネントは /book.xhtml ビューを表示するときに単に現在の対話を伝播します。
|
ここまでは対話をネストする方法を見てきました。 次のコードでは、
HotelBookingAction の動作を拡張して選択した部屋の予約を確認する方法を示しています。
例1.33 HotelBookingAction.java
@Stateful
@Name("hotelBooking")
@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() {}
}
|
動作に @End(root=true) アノテーションを付けるとルートの対話を終了させます。 これは効率的に対話スタック全体を破棄します。 対話が終了したらその中にネストされた対話も終了されます。 ルートはオリジナルの対話であるため、これが予約の確認が終了したら作業領域に関連付けられたすべての状態を破棄して解放するシンプルな方法です。
|
| roomSelection はユーザー確認で booking にのみ関連付けられます。 ネストされた対話コンテキストに値をアウトジェクトしても外部の対話には影響ありませんが、 外側の対話からインジェクトされるオブジェクトは参照によりインジェクトされます。 つまり、 これらのオブジェクトに対する変更はすべて親の対話で反映されるだけでなく、 他の同時にネストされた対話にも反映されます。
|
|
取り消し動作に @End(root=true, beforeRedirect=true) アノテーションを付与するだけで、 作業領域に関連するすべての状態を容易に破棄、 解放してからユーザーをホテル選択ビューにリダイレクトさせることができます。
|
ぜひアプリケーションをデプロイしてご自身でテストしてみてください。多くのウィンドウやタブを開き、 さまざまなホテルと部屋の組み合わせで試してみてください。予約の確認作業を行うとネストされた対話モデルにより常に正しいホテルと部屋が表示されます。



