SeamFramework.orgCommunity Documentation

第7章 対話とワークスペースの管理

7.1. Seam の対話モデル
7.2. ネストされた対話
7.3. GET 要求を使って対話を開始する
7.4. <s:link><s:button> の使いかた
7.5. 成功のメッセージ
7.6. ナチュラル対話の ID
7.7. ナチュラル対話を作成する
7.8. ナチュラル対話にリダイレクトする
7.9. ワークスペースの管理
7.9.1. ワークスペース管理と JSF ナビゲーション
7.9.2. ワークスペース管理と jPDL ページフロー
7.9.3. 対話切り替え
7.9.4. 対話一覧
7.9.5. ブレッドクラム (Breadcrumbs)
7.10. 対話型コンポーネントと JSF コンポーネントのバインディング
7.11. 対話的コンポーネントへの並列コール
7.11.1. 対話的 AJAX アプリケーションを設計する
7.11.2. エラーを処理する
7.11.3. RichFaces (Ajax4jsf)

本章では、 そろそろ Seam の対話モデルについて詳細に理解していくことにします。

事のはじまりは、 3 つの思いつきが融合した結果、 Seam 「対話」の概念となったことです。

こうした思いつきを統一しフレームワークで強力なサポートを提供することで、 以前よりすっきりしたコードでより豊かで効率的なアプリケーションをビルドできるパワフルな構成概念を得ました。

これまでに見た例は、以下の規則に従う非常に単純な対話モデルを利用します。

Seam は透過的に対話コンテキスト (一時的な対話コンテキストを含む) を JSF ポストパックおよびリダイレクト全体に伝播します。 特に何もしなければ 非 faces 要求 (たとえば GET 要求など) は対話コンテキストを伝播せず新しい一時的な対話内で処理されます。 常にとは限りませんが、 これが通常求められる動作になります。

non-faces 要求全体に Seam 対話を伝播させたい場合、 要求パラメータとして Seam 対話 ID (conversation id) を明示的にコード化する必要があります。


<a href="main.jsf?#{manager.conversationIdParameter}=#{conversation.id}"
>Continue</a
>

JSF のようにする場合には以下のようにします。


<h:outputLink value="main.jsf">
    <f:param name="#{manager.conversationIdParameter}" value="#{conversation.id}"/>
    <h:outputText value="Continue"/>
</h:outputLink
>

Seam タグライブラリを使用する場合、 以下は等価です。


<h:outputLink value="main.jsf">
    <s:conversationId/>
    <h:outputText value="Continue"/>
</h:outputLink
>

ポストバック用の対話コンテキストの伝播を無効にしたい場合は、 同様のトリックが使えます。


<h:commandLink action="main" value="Exit">
    <f:param name="conversationPropagation" value="none"/>
</h:commandLink
>

Seam タグライブラリを使用する場合、 以下は等価です。


<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="none"/>
</h:commandLink
>

対話コンテキストの伝播を無効にすることと、 対話を終了することとは全く異なることですので注意してください。

conversationPropagation 要求パラメータ または <s:conversationPropagation> タグは、 対話の開始と終了、あるいはネストされた対話の開始にも使用することができます。


<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="end"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Child">
    <s:conversationPropagation type="nested"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Hotel">
    <s:conversationPropagation type="begin"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Hotel">
    <s:conversationPropagation type="join"/>
</h:commandLink
>

この対話モデルは、 マルチウィンドウ操作に関して正常に動作するアプリケーションの構築を容易してくれます。 多くのアプリケーションにとって必要なものはこれだけです。 複雑なアプリケーションのなかには以下の追加要件の両方あるいはどちらかを必要とするものがあります。

ネストされた対話は既存の対話のスコープ内で @Begin(nested=true) とマークされたメソッドを呼び出すことによって作成されます。 ネストされた対話はそれ自体の対話コンテキストを持っていますが、 外側の対話のコンテキストから値を読み取ることができます。 オブジェクトは参照により取得されるため外側の対話のコンテキストはネストされた対話内では読み取り専用となりますが、 オブジェクト自体への変更はその外側のコンテキストに反映されます。

次に @End が出てくると、 対話スタックから「ポップ(Pop)」されることで、 その取り出された対話は破棄されます。 対話は任意の深さにネストすることができます。

特定のユーザーアクティビティ (ワークスペース管理や戻るボタン) により、 内側の対話が終了する前に外側の対話が開始させることができます。 この場合、 同じ外側の対話に属する複数の並列ネスト対話を持つことができます。 ネストされた対話が終了する前に外側の対話が終了すると、 Seam はネストされた対話コンテキストを外側のコンテキストと共にすべて破棄します。

対話スタックの最下位にある対話がルートの対話になります。 この対話を破棄すると常に派生した対話はすべて破棄されます。 @End(root=true) を指定すると宣言的にこれを行うことができます。

対話は継続可能な状態と見なすことができます。 ネストされた対話により、 ユーザーとの交信でのさまざまなポイントにおいてアプリケーションは一貫した継続可能な状態を捕らえることができるようになります。 従って、 戻るボタンを押すことやワークスペース管理に対して正しい動作を保証します。

前述した通り、 現在ネストされている対話の親となる対話にコンポーネントが存在する場合、 このネストされている対話は同じインスタンスを使用します。ときには、 親となる対話内に存在するコンポーネントインスタンスがその子となる対話からは見えなくなるように、 ネストされるそれぞれの対話内に別々のインスタンスを持たせると便利なことがあります。 コンポーネントに @PerNestedConversation アノテーションを付けるとこれを行うことができます。

ページが non-faces 要求 (例、 HTTP GET 要求) 経由でアクセスされる場合、 JSF は起動されるアクションリスナーを全く定義しません。 ユーザーがそのページをブックマークする、あるいは <h:outputLink> からそのページに行き着く場合などに発生します。

ページがアクセスされたら直ちに対話を開始したい場合があります。 JSF アクションメソッドがないため、 アクションに @Begin アノテーションを付けるという普通の方法では問題を解決することができません。

このページが状態をコンテキスト変数にフェッチする必要がある場合、 さらなる問題が発生します。 すでに、2 つの問題解決方法を見てきました。 Seam コンポーネントにその状態が保持される場合、 @Create メソッドでその状態をフェッチできます。 保持されていなければ、 コンテキスト変数に対して @Factory メソッドを定義することができます。

これらのオプションがうまくいかない場合、 Seam では pages.xml ファイルに ページアクション を定義することができます。


<pages>
    <page view-id="/messageList.jsp" action="#{messageManager.list}"/>
    ...
</pages
>

ページがレンダリングされるようとするときは常に、 応答のレンダリングフェーズの冒頭でこのアクションメソッドが呼び出されます。 ページアクションが null 以外の結果を返す場合、 Seam は適切な JSF および Seam ナビゲーションの規則を処理するので、 まったく異なるページがレンダリングさることになるかもしれません。

ページのレンダリング前に行いたいことが対話の開始だけなら、 組み込みアクションメソッドを次のように使用できます。


<pages>
    <page view-id="/messageList.jsp" action="#{conversation.begin}"/>
    ...
</pages
>

また、 この組み込みアクションは JSF コントロールからも呼び出すことができ、 同様に #{conversation.end} を使って対話を終了することができます。

既存の対話にジョインするあるいはネストされる対話を開始する、 ページフローまたはアトミック対話を開始するためにさらに制御が必要な場合は、 <begin-conversation> エレメントを使用してください。


<pages>
    <page view-id="/messageList.jsp">
       <begin-conversation nested="true" pageflow="AddItem"/>
    <page>
    ...
</pages
>

また、 <end-conversation> エレメントもあります。


<pages>
    <page view-id="/home.jsp">
       <end-conversation/>
    <page>
    ...
</pages
>

1 番目の問題を解決するには、 現在 5 つのオプションから選択できます。

JSF コマンドリンクは常に JavaScript でフォームサブミットを行います。 これによりウェブブラウザの「新しいウィンドウで開く」または「新しいタブで開く」機能を動作させなくしてしまいます。 プレーンの JFS でこの機能が必要な場合は、 <h:outputLink> を使用する必要があります。 ただし、 <h:outputLink> には重要な制限が 2 つあります。

Seam provides the notion of a page action to help solve the first problem, but this does nothing to help us with the second problem. We could work around this by using the RESTful approach of passing a request parameter and requerying for the selected object on the server side. In some cases — such as the Seam blog example application — this is indeed the best approach. The RESTful style supports bookmarking, since it does not require server-side state. In other cases, where we don't care about bookmarks, the use of @DataModel and @DataModelSelection is just so convenient and transparent!

この機能を補ってさらに対話伝播の管理をより簡略化するために、 Seam は <s:link> JSF タグを提供しています。

このリンクは JSF ビュー ID だけを指定することができます。


<s:link view="/login.xhtml" value="Login"/>

あるいは、 アクションメソッドを指定することができます (この場合、 アクションの結果は最終的なページを確定する)。


<s:link action="#{login.logout}" value="Logout"/>

JSF ビュー ID とアクションメソッドの両方を指定すると、 アクションメソッドが null 以外の結果を返さない限り「ビュー」が使用されます。


<s:link view="/loggedOut.xhtml"  action="#{login.logout}" value="Logout"/>

リンクは <h:dataTable> 内で使用する DataModel の選択列を自動的に伝播します。


<s:link view="/hotel.xhtml" action="#{hotelSearch.selectHotel}" value="#{hotel.name}"/>

既存の対話のスコープを残しておくことができます。


<s:link view="/main.xhtml" propagation="none"/>

対話を開始、 終了、 またはネストすることができます。


<s:link action="#{issueEditor.viewComment}" propagation="nest"/>

リンクが対話を開始すると、 使用されるページプローを指定することもできます。


<s:link action="#{documentEditor.getDocument}" propagation="begin"
        pageflow="EditDocument"/>

The taskInstance attribute is for use in jBPM task lists:


<s:link action="#{documentApproval.approveOrReject}" taskInstance="#{task}"/>

(上記の例は DVD ストアデモアプリケーションを参照してください。)

最後に、 ボタンとしてレンダリングされる「リンク」が必要な場合は <s:button> を使用します。


<s:button action="#{login.logout}" value="Logout"/>

動作に対して成功したか失敗したかをユーザーに示すメッセージを表示するのは非常に一般的です。 これには、 JSF FacesMessage を使うと便利です。 残念ながら、 成功のアクションはブラウザリダイレクトを要することが多く、 JSF はリダイレクト全体に faces のメッセージは伝播しません。 このためプレーン JSF で成功のメッセージを表示するのはかなり困難になります。

組み込みの対話スコープ Seam コンポーネントである facesMessages がこの問題を解決してくれます。 (Seam リダイレクトフィルタをインストールしておく必要があります。)

@Name("editDocumentAction")

@Stateless
public class EditDocumentBean implements EditDocument {
    @In EntityManager em;
    @In Document document;
    @In FacesMessages facesMessages;
    
    public String update() {
        em.merge(document);
        facesMessages.add("Document updated");
    }
}

facesMessages に追加されるメッセージはすべてすぐ次のフェースである応答レンダリングフェーズで現在の対話に対して使用されます。 これは Seam がリダイレクト全体に一時的な対話コンテキストを維持するので長期実行の対話がない場合でも機能します。

JSF EL 式を faces メッセージサマリーに含ませることもできます。

facesMessages.add("Document #{document.title} was updated");

たとえば、 通常の方法でメッセージを表示することができます。


<h:messages globalOnly="true"/>

永続オブジェクトを処理する対話を作業する場合は標準のサロゲート対話 ID ではなくそのオブジェクトのナチュラルキーを使用する方が望ましい場合があります。

既存の対話に容易にリダイレクト

ユーザーが同じ操作を 2 回要求する場合は既存の対話にリダイレクトすると便利な場合があります。 例をあげてみます。 「 「ebay で両親へのクリスマスプレゼントを獲得しそのアイテムの支払いプロセスが途中まで進んでいます。 ここでこのアイテムが直接両親に配達されるようにしようと思います。 支払い詳細は入力しましたが両親の明確な住所を思い出すことができません。 住所を検索しようとして誤って同じブラウザウィンドウを使ってしまいました。 もう一度さっきのアイテムの支払いに戻る必要があります。」

With a natural conversation it's really easy to have the user rejoin the existing conversation, and pick up where they left off - just have them to rejoin the payForItem conversation with the itemId as the conversation id.

ユーザーフレンドリーな URL

個人的には操作可能な階層と (URL を編集しながら操作可能) 意味のある URL (この Wiki に見られるようにランダムな ID での識別を行わない) で構成されているように見えますが、 アプリケーションユーザーにとってはフレンドリーな URL はそれほど重要ではない場合もあるでしょう。

With a natural conversation, when you are building your hotel booking system (or, of course, whatever your app is) you can generate a URL like http://seam-hotels/book.seam?hotel=BestWesternAntwerpen (of course, whatever parameter hotel maps to on your domain model must be unique) and with URLRewrite easily transform this to http://seam-hotels/book/BestWesternAntwerpen.

かなり改善されたはずです。

ナチュラル対話は pages.xml で定義されます。


  <conversation name="PlaceBid"
                  parameter-name="auctionId"
                  parameter-value="#{auction.auctionId}"/>

上記の定義でまず判るのは対話に名前があることです。 この場合 PlaceBid になります。 名前は特定の指定対話を固有に識別するため page 定義により参加する指定対話の識別に使用されます。

次の属性 parameter-name はデフォルトの対話 ID パラメータの代わりにナチュラル対話の ID を含む要求パラメータを定義します。 この例では parameter-nameauctionId になります。 つまり、 ページの URL 内に出現する cid=123 のような対話パラメータの代わりに auctionId=765432 を含むようになります。

上記の設定の最後の属性 parameter-value は対話 ID として使用するナチュラルビジネスキーの値の評価に使用される EL 式を定義します。 この例では対話 ID は現在範囲内の auction のプライマリーキー値になります。

次に指定対話に参加するページを定義します。 page 定義の conversation 属性を指定してこれを行います。


  <page view-id="/bid.xhtml" conversation="PlaceBid" login-required="true">
      <navigation from-action="#{bidAction.confirmBid}"
>        
          <rule if-outcome="success">
              <redirect view-id="/auction.xhtml">
                  <param name="id" value="#{bidAction.bid.auction.auctionId}"/>
              </redirect>
          </rule
>        
      </navigation>
  </page
>

ナチュラル対話を開始またはリダイレクトする場合、 ナチュラル対話名を指定するいくつかのオプションがあります。 次のページ定義をまず見てみることにします。


  <page view-id="/auction.xhtml">
    <param name="id" value="#{auctionDetail.selectedAuctionId}"/>
       
    <navigation from-action="#{bidAction.placeBid}">
      <redirect view-id="/bid.xhtml"/>
    </navigation>
  </page
>

auction ビューからアクション #{bidAction.placeBid} を呼び出していること (これらのサンプルはすべて Seam にある seamBay から取得している)、 前述のようにナチュラル対話 PlaceBid で設定される /bid.xhtml にリダイレクトしていることがわかります。 アクションメソッドの宣言は以下のようになります。

   @Begin(join = true)

   public void placeBid()

名前が付けられた対話が <page/> エレメントに指定される場合、 その名前の対話へのリダイレクトはアクションメソッドが完全に呼び出された後でナビゲーションルールの一部として発生します。 既存の対話にリダイレクトする場合はアクションメソッドが呼び出される前にリダイレクトが発生する必要があるため問題となってきます。 したがってアクションが呼び出される場合は対話名を指定する必要があります。 s:conversationName タグを使って行うのがその方法のひとつとなります。


  <h:commandButton id="placeBidWithAmount" styleClass="placeBid" action="#{bidAction.placeBid}">
    <s:conversationName value="PlaceBid"/>
  </h:commandButton
>

もうひとつの方法は s:link または s:button のどちらかを使用するときに conversationName を指定する方法です。


  <s:link value="Place Bid" action="#{bidAction.placeBid}" conversationName="PlaceBid"/>

ワークスペース管理は、1 つのウィンドウの中で対話を「切り換える」能力です。 Seam はワークスペース管理を Java コードのレベルで完全に透過的にします。 ワークスペース管理を可能にするために、必要なすべては以下の通りです。

対話一覧は対話切り替えに非常によく似ていますが、 表形式で表示される点が異なります。


<h:dataTable value="#{conversationList}" var="entry"
        rendered="#{not empty conversationList}">
    <h:column>
        <f:facet name="header"
>Workspace</f:facet>
        <h:commandLink action="#{entry.select}" value="#{entry.description}"/>
        <h:outputText value="[current]" rendered="#{entry.current}"/>
    </h:column>
    <h:column>
        <f:facet name="header"
>Activity</f:facet>
        <h:outputText value="#{entry.startDatetime}">
            <f:convertDateTime type="time" pattern="hh:mm a"/>
        </h:outputText>
        <h:outputText value=" - "/>
        <h:outputText value="#{entry.lastDatetime}">
            <f:convertDateTime type="time" pattern="hh:mm a"/>
        </h:outputText>
    </h:column>
    <h:column>
        <f:facet name="header"
>Action</f:facet>
        <h:commandButton action="#{entry.select}" value="#{msg.Switch}"/>
        <h:commandButton action="#{entry.destroy}" value="#{msg.Destroy}"/>
    </h:column>
</h:dataTable
>

恐らく、 多くの方が独自のアプリケーションに合うようカスタマイズを希望するだろうと思います。

詳細を持つ対話のみがこの一覧に含まれます。

対話一覧によりユーザーがワークスペースを破壊することができるので留意してください。

重要ではありませんが、 JSF コンポーネントへのバインディングの保持には使用できないという制限が対話型コンポーネントにはあります。 (一般的には、 アプリケーション論理からビューに対する強い依存関係を作ってしまうため、 絶対的に必要でない限り一般的にはこの JSF の機能は使用しないようにした方がよいでしょう。) postback 要求で、 Seam 対話コンテキストが復元される前、 ビューの復元フェーズ中にコンポーネントのバインディングは更新されます。

これを回避するには、 イベントスコープコンポーネントを使ってコンポーネントバインディングを格納し、 それを必要とする対話スコープコンポーネントにインジェクトします。

@Name("grid")

@Scope(ScopeType.EVENT)
public class Grid
{
    private HtmlPanelGrid htmlPanelGrid;
    // getters and setters
    ...
}
@Name("gridEditor")

@Scope(ScopeType.CONVERSATION)
public class GridEditor
{
    @In(required=false)
    private Grid grid;
    
    ...
}

また、 対話スコープのコンポーネントを JSF コントロールのバインド先となるイベントスコープのコンポーネントにはインジェクトできません。 これには facesMessages のようなコンポーネント内での Seam 構築が含まれます。

Alternatively, you can access the JSF component tree through the implicit uiComponent handle. The following example accesses getRowIndex() of the UIData component which backs the data table during iteration, it prints the current row number:



<h:dataTable id="lineItemTable" var="lineItem" value="#{orderHome.lineItems}">
   <h:column>
      Row: #{uiComponent['lineItemTable'].rowIndex}
   </h:column>
   ...
</h:dataTable
>

JSF UI のコンポーネントはこのマップ内のクライアント識別子で使用可能になります。

Seam コンポーネントへの並列コールに関する全般は 項4.1.10. 「同時並行処理モデル」 でご覧ください。 ここでは並列が発生する最も一般的な状況について説明しています (AJAX 要求から対話的コンポーネントにアクセスする)。 クライアントで発生したイベントの制御に Ajax クライアントライブラリが提供すべきオプションについて説明してから RichFaces で提供されるオプションについて見ていきます。

対話的コンポーネントでは実際の並列アクセスは許可されないので、 Seam は要求を連続的に処理するようそれぞれを待ち行列に入れます。 これにより各要求は決定論的に実行されるようになります。 ただし、 シンプルなキューはあまりよくありません。 まず、 メソッドがなんらかの理由で完了に長時間かかっている場合、 クライアントが要求を生成するたび繰り返してそれを実行するのはよくありません (サービス妨害攻撃の可能性)。 また、 ユーザーに対するステータスのクィックアップデートに AJAX が頻繁に使用されるためアクションを長時間実行し続けるのは実用的ではありません。

したがって、 長時間実行の対話の内側で作業する場合は Seam は一定期間アクションイベントを待ち行列に入れます (並列要求タイムアウト)。 時間通りにイベントを処理できないと一時的な対話を作成してユーザーに状況を知らせるメッセージを出力します。 このため、 AJAX イベントでサーバーを溢れさせないようにすることが非常に重要となります。

components.xml で並列要求のタイムアウトにミリ秒単位で適当なデフォルトを設定することができます。


<core:manager concurrent-request-timeout="500" />

また、 ページごとに並列要求のタイムアウトを微調整することもできます。


<page view-id="/book.xhtml" 
         conversation-required="true" 
         login-required="true"
         concurrent-request-timeout="2000" />

ここまではユーザーに対して連続的に出現する AJAX 要求について説明してきました。 クライアントはサーバーにイベントが発生したことを伝え、 その結果に応じてページの一部を表示します。 この方法は AJAX が軽量である場合には最適となります (1 コラム内の数字の合計を計算するなど呼び出されるメソッドがシンプルである場合)。 しかし、 計算に 1 分を要するような複雑な計算が必要な場合はどうでしょう。

重量のある計算にはポーリングベースの方法を使用してください。 クライアントがサーバーに AJAX 要求を送信し、 これによりサーバーで非同期にアクションが実行され (クライアントへの応答が即時となる)、 クライアントは次にサーバーに更新をポーリングします。 それぞれのアクションがすべて実行する (いずれもタイムアウトしない) ことが重要となるような長時間実行のアクションがある場合に適した方法となります。

まず、 簡単な「連続」要求を使用するのかどうか、 あるいはポーリングの方法を使用するのかどうかを決める必要があります。

「連続」要求を選択する場合は要求が完了する長さを推測する必要があります。 並列要求のタイムアウトに比べ十分に短い時間で完了するかどうか推測してみます。 不十分な場合はこのページの並列要求のタイムアウトを変更します (前述の通り)。 サーバーを要求で溢れさせないようにするためクライアント側で行列待ちをさせた方がよいでしょう。 イベントが頻繁に発生し (入力フィールドの keypress や onblur など) クライアントの即時更新が優先事項ではない場合はクライアント側で要求の遅延をセットしてください。 要求遅延の作業を行う場合、 イベントがサーバー側でも行列待ちできるようにすることを考慮に入れてください。

最後に、 クライアントライブラリは未完了の重複要求を最新のものを残してすべて停止するオプションを提供することができます。

ポールスタイルのデザインを使用する場合は細かな調整があまり必要ありません。 アクションメソッド @Asynchronous をマークしてポーリングの間隔を決定するだけです。

int total;


// This method is called when an event occurs on the client
// It takes a really long time to execute
@Asynchronous      
public void calculateTotal() {
   total = someReallyComplicatedCalculation();
}
// This method is called as the result of the poll
// It's very quick to execute
public int getTotal() {
   return total;
}

並列要求を対話的コンポーネントに対して行列待ちに入れるよう十分注意をしてアプリケーションを設定しても、 サーバー側がオーバーロードとなり要求処理にかかる時間が concurrent-request-timeout を越えるまでに全要求を処理できなくなる危険性があります。 こうした場合、 Seam は pages.xml で処理が可能な ConcurrentRequestTimeoutException を送出します。 HTTP 503 エラーの送信をお勧めします。


   <exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
      <http-error error-code="503" />
   </exception>

代わりにエラーページにリダイレクトすることができます。


<exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
   <end-conversation/>
   <redirect view-id="/error.xhtml">
      <message>The server is too busy to process your request, please try again later</message>
   </redirect>
</exception>

ICEfaces, RichFaces Ajax and Seam Remoting can all handle HTTP error codes. Seam Remoting will pop up a dialog box showing the HTTP error. ICEfaces will indicate the error in its connection status component. RichFaces provides the most complete support for handling HTTP errors by providing a user definable callback. For example, to show the error message to the user:

<script type="text/javascript">
   A4J.AJAX.onError = function(req,status,message) { 
      alert("An error occurred");
   };
</script>

If instead of an error code, the server reports that the view has expired, perhaps because the session timed out, you use a separate callback function in RichFaces to handle this scenario.

<script type="text/javascript">
   A4J.AJAX.onExpired = function(loc,message) { 
      alert("View expired");
   };
</script>

Alternatively, you can allow RichFaces handle the error, in which case the user will be presented with a prompt that reads "View state could't be restored - reload page?" You can customize this message globally by setting the following message key in an application resource bundle.

AJAX_VIEW_EXPIRED=View expired. Please reload the page.

RichFaces (Ajax4jsf) is the Ajax library most commonly used with Seam, and provides all the controls discussed above: