SeamFramework.orgCommunity Documentation

第25章 リモーティング

25.1. 設定
25.2. "Seam"オブジェクト
25.2.1. Hello World サンプル
25.2.2. Seam.Component
25.2.3. Seam.Remoting
25.3. EL 式を評価する
25.4. クライアントのインタフェース
25.5. コンテキスト
25.5.1. 対話 ID の設定と読み込み
25.5.2. 現在の対話スコープ内のリモート呼び出し
25.6. バッチ要求
25.7. データタイプの取り扱い
25.7.1. プリミティブ型 / 基本タイプ
25.7.2. JavaBeans
25.7.3. 日付と時刻
25.7.4. Enum
25.7.5. 集合
25.8. デバッグ機能
25.9. Handling Exceptions
25.10. メッセージをロードする
25.10.1. メッセージを変更する
25.10.2. ローディングメッセージを隠す
25.10.3. カスタムのローディングインジケータ
25.11. 戻り値の制御
25.11.1. フィールドの制約
25.11.2. Map とコレクションの制約
25.11.3. 特定タイプのオブジェクトを制約する
25.11.4. 制約同士を組み合わせる
25.12. Transactional Requests
25.13. JMS メッセージング
25.13.1. 設定
25.13.2. JMS Topic のサブスクライブ
25.13.3. トピックのサブスクライブを中止する
25.13.4. ポーリングのプロセスの調整

Seam は、 Web ページから AJAX (Asynchronous Javascript and XML) を使用してコンポーネントにリモートアクセスする便利な方法を提供します。 この機能を実現する Seam では、 開発時に労力がかからないようになっています - コンポーネントに必要なものは、 AJAX を通じてアクセス可能とするための単純なアノテーションだけです。 この章では、 AJAX 可能な Web ページを作るために必要なステップについて述べ、 そしてSeam Remoting フレームワークの機能についても詳しく説明します。

リモーティングの機能を使用するには、まずweb.xmlファイル内でSeam Resourceサーブレットを設定する必要があります。


<servlet>
  <servlet-name
>Seam Resource Servlet</servlet-name>
  <servlet-class
>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name
>Seam Resource Servlet</servlet-name>
  <url-pattern
>/seam/resource/*</url-pattern>
</servlet-mapping
>

次のステップは、Webページに必要なJavaScriptをインポートすることです。インポートすべきスクリプトが最低二つあります。最初の一つは、リモーティングの機能を有効にする、クライアントサイドフレームワークのすべてのコードを含みます:


<script type="text/javascript" src="seam/resource/remoting/resource/remote.js"
></script
>

二つ目のスクリプトは、 呼び出したいコンポーネントに対するスタブと型定義を含みます。 それはコンポーネントのローカルインタフェースをもとにして動的に生成され、 インタフェースのリモートメソッドを呼び出す際に使用されるすべてのクラスに対する型定義を内包しています。 スクリプトの名前はコンポーネントの名前が反映されます。 例えば、 @Name("customerAction") というアノテーション付きのステートレスセッション Bean を持つ場合、 スクリプトタグは以下のようになります。


<script type="text/javascript" 
          src="seam/resource/remoting/interface.js?customerAction"
></script
>

同じページから一つ以上のコンポーネントにアクセスしたい場合は、スクリプトタグのパラメータとしてそれらをすべて含めます。


<script type="text/javascript" 
        src="seam/resource/remoting/interface.js?customerAction&accountAction"
></script
>

代わりに、 必要な Javascript のインポートに s:remote を使用することもできます。 インポートするコンポーネントあるいはクラス名をそれぞれコンマで区切ります。



  <s:remote include="customerAction,accountAction"/>    
    

クライアント側からのコンポーネントとのやりとりは、 すべて Seam Javascript オブジェクト経由で行われます。 このオブジェクトは remote.js に定義され、 コンポーネントに対する非同期呼び出しに使用します。 オブジェクトは 二つの機能に区分されます。 コンポーネントと連携するメソッドを含む Seam.Component そして、リモート要求を実行するメソッドを含む Seam.Remoting です。 このオブジェクトに精通する一番容易な方法は、 簡単なサンプルから始めることです。

Seam オブジェクトがどのように動作するかを見るために、 簡単なサンプルを通じて一歩を踏み出してみましょう。 まず最初に、helloAction と呼ばれる新しい Seam コンポーネントを作成しましょう。

@Stateless

@Name("helloAction")
public class HelloAction implements HelloLocal {
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

新しいコンポーネント用にローカルインタフェースも生成する必要があります。 @WebRemote アノテーションに特に注意してください。 リモートによるメソッドへのアクセスを可能とするために必要です。

@Local

public interface HelloLocal {
  @WebRemote
  public String sayHello(String name);
}

That's all the server-side code we need to write.

Now for our web page - create a new page and import the helloAction component:


<s:remote include="helloAction"/>

ユーザーにとって完全にインテラクティブとなるようページにボタンを追加してみます。


<button onclick="javascript:sayHello()"
>Say Hello</button
>

ボタンをクリックしたとき、実際にボタンに何かを行わせるためのスクリプトをもう少し追加する必要があります。


<script type="text/javascript">
  //<![CDATA[

  function sayHello() {
    var name = prompt("What is your name?");
    Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);
  }

  function sayHelloCallback(result) {
    alert(result);
  }

   // ]]>
</script
>

作業完了です! アプリケーションをデプロイして、ページを見てみましょう。 ボタンをクリックして、プロンプトが出たら名前を入力しましょう。 呼び出しの成功を確認するための hello メッセージが、メッセージボックスに表示されます。 少し時間を節約したいのであれば、 Seam の /examples/remoting/helloworld ディレクトリにこの Hello World サンプルの全ソースコードがあります。

ところで、このスクリプトのコードは何をするのでしょうか。 もっと細かく分解してみましょう。手始めに、二つのメソッドを実装した Javascript コードから見ていきましょう。 最初のメソッドはユーザーに対して名前を入力するよう促し、リモート要求を行うのがその役割です。 以下の行から見てみましょう。


Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);

この行の最初の部分 Seam.Component.getInstance("helloAction") は、 helloAction コンポーネントのプロキシ、あるいは"スタブ"を返します。 このスタブに対してコンポーネントのメソッド呼び出しが可能です。 それは、まさにこの行の残りの部分 sayHello(name, sayHelloCallback); になります。

コード行全体で行っていることは、コンポーネントのsayHelloメソッドの呼び出しと、 パラメータとしてnameを渡すことです。 二番目のパラメータsayHelloCallbackは、 このコンポーネントの sayHelloメソッドのパラメータではありません。 その代わり、Seam Remoting フレームワークが要求に対する応答を受けたら、 それを sayHelloCallback Javascript メソッドに渡すべきことを指示します。 このコールバックパラメータは完全にオプションです。 戻り値 void のメソッドを呼び出す場合、 あるいは結果を気にする必要がない場合は、 遠慮なくそのままにしてください。

sayHelloCallbackメソッドが、リモート要求に対するレスポンスを受信した場合、 メソッド呼び出しの結果を表示するアラートメッセージが現れます。

Seam.Component Javascript オブジェクトは、 Seam コンポーネントと連携する多くのクライアントメソッドを提供します。 主な二つのメソッド、newInstance()getInstance()は、 以降の章にも記述されていますが、 これらの主な違いは、newInstance() は、いつもコンポーネントタイプの新しいインスタンスを生成し、そして、getInstance()は、シングルトンのインスタンスを返すことです。

新しいエンティティ、あるいは、JavaBean コンポーネントインスタンスを生成するためにこのメソッドを使用します。 このメソッドにより返されるオブジェクトは、 サーバサイドの対応するものと同じ getter/setter メソッドを持つか、 あるいは、代替として、お望みならば、そのフィールドに直接アクセスが可能です。 例として、以下の Seam エンティティ コンポーネントをご覧ください。

@Name("customer")

@Entity
public class Customer implements Serializable
{
  private Integer customerId;
  private String firstName;
  private String lastName;
    
  @Column public Integer getCustomerId() { 
    return customerId; 
  }
    
  public void setCustomerId(Integer customerId} { 
    this.customerId = customerId; 
  }
  
  @Column public String getFirstName() { 
    return firstName; 
  }
  
  public void setFirstName(String firstName) {
    this.firstName = firstName; 
  }
  
  @Column public String getLastName() {
    return lastName;
  }
  
  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

クライアントサイド Customer を生成するために、以下のコードを記述します。


var customer = Seam.Component.newInstance("customer");

そして、ここからは customer オブジェクトのフィールドの設定が可能です。


customer.setFirstName("John");
// Or you can set the fields directly
customer.lastName = "Smith";

Seam Remoting は EL 式の評価にも対応します。 サーバからのデータ取得にもうひとつ便利なメソッドを提供します。 Seam.Remoting.eval() 関数を使用して、 EL 式をサーバ上で遠隔に評価してその結果値をクライアント側のコールバックメソッドに返すことができます。 この関数は二つのパラメータを受け取ります。 一番目のパラメータは 評価対象となる EL 式となり、 二番目のパラメータはその式の値を付けて呼び出すコールバックメソッドになります。 次に例を示します。


  function customersCallback(customers) {
    for (var i = 0; i < customers.lengthi++) {
      alert("Got customer: " + customers[i].getName());
    }    
  }
    
  Seam.Remoting.eval("#{customers}"customersCallback);  
    

この例では、 #{customers} の式が Seam によって評価され、 その式の値 (この場合 Customer オブジェクトの一覧) がcustomersCallback() メソッドに返されます。 このようにして返されるオブジェクトは Javascript で動作できるようそれ自体のタイプがインポートされていなければなりません (s:remote)。 したがって、 customer オブジェクトの一覧と動作させるには、 customer タイプをインポートする必要があります。


<s:remote include="customer"/>

上記の設定のセクションでは、 インタフェースまたはコンポーネントの「スタブ」は seam/resource/remoting/interface.js 経由でページにインポートするか、 s:remote を使用してインポートします。


<script type="text/javascript" 
        src="seam/resource/remoting/interface.js?customerAction"
></script
>

<s:remote include="customerAction"/>

ページにこのスクリプトをインクルードすることにより、 コンポーネントのためのインタフェース定義に加えて、コンポーネントのメソッドを実行するために必要なその他のコンポーネントとタイプが生成され、リモーティングフレームワークで使用可能になります。

生成可能なクライアントスタブには「実行可能」スタブと「タイプ」スタブの二タイプがあります。 実行可能スタブは動作を持ち、 セッション Bean コンポーネントに対するメソッドの実行に使用されます。 一方、 タイプスタブは状態を保持し、 パラメータとして渡すまたは結果として返すことができるタイプを表します。

生成されるクライアントスタブのタイプは Seam コンポーネントのタイプにより異なります。 コンポーネントがセッション Bean の場合、 実行可能スタブが生成されます。 これ以外、 エンティティや JavaBean となる場合にはタイプスタブが生成されます。 この規則には例外がひとつあります。 コンポーネントが JavaBean (つまり、セッション Bean や エンティティ Bean ではない場合) であり、 そのメソッドのいずれにも @WebRemote アノテーションが付く場合、 タイプスタブではなく実行可能スタブが生成されます。 これにより、 セッション Bean にアクセスできない非 EJB 環境で JavaBean コンポーネントのメソッドを呼び出すリモーティングが使用できるようになります。

Seam リモートコンテキストは、 リモーティングの要求 / 応答サイクルの一部として送受信される追加情報を含んでいます。 現段階では対話 ID だけしか含んでいませんが、将来拡張される可能性があります。

Seam リモーティングは、複数のコンポーネント呼び出しが一つの要求内で実行されることを可能にします。 ネットワークトラフィックを減少することが適切であれば、 どこでもこの特徴を使用することを推奨します。

Seam.Remoting.startBatch()メソッド は、 新たなバッチを起動します。 バッチ起動後に実行されたコンポーネント呼び出しは、 即座に送られるのではなく、キューイングされます。 必要とされるすべてのコンポーネント呼び出しがバッチに追加されたとき、Seam.Remoting.executeBatch()メソッドは、 サーバにキューイングされた呼び出しすべてを含む一つの要求を送信するでしょう。 そして、そこで順番に実行されます。 呼び出しが実行された後、 すべての戻り値を含む一つの応答は、 クライアントに返され、コールバック機能が (もし、設定されていれば) 実行と同じ順番で起動されます。

startBatch()メソッドで新たなバッチを起動したが要求を送らないことにした場合、 Seam.Remoting.cancelBatch()メソッドはキュー待ちしているすべての呼び出しを破棄してそのバッチモードを終了します。

バッチが利用されているサンプルを見るには、/examples/remoting/chatroomを参照ください。

一般的に JavaBeans は Seam エンティティ、 JavaBean コンポーネント、 または non-component クラスのいずれかになります。 オブジェクトの新しいインスタンスの生成には適切なメソッドを使用してください (Seam コンポーネントには Seam.Component.newInstance()、 これ以外は Seam.Remoting.createType())。

パラメータが、このセクションの別の場所で記述されたその他の有効なタイプの一つではない場合、 これら二つのメソッドのどちらかによって生成されるオブジェクトだけがパラメータ値として使用されるべきであることに気づくことが重要です。 いくつかの状況では、 以下のように厳密にパラメータタイプを決定できないコンポーネントメソッドがあるかもしれません。

@Name("myAction")

public class MyAction implements MyActionLocal {
  public void doSomethingWithObject(Object obj) {
    // code
  }
}

この場合、 myWidget コンポーネントのインスタンスを渡したいところですが、 myAction のインタフェースはそのいずれのメソッドからも直接参照されないため myWidget を含みません。 これを回避するには、 MyWidget を明示的にインポートする必要があります。


<s:remote include="myAction,myWidget"/>

これにより myWidget オブジェクトが Seam.Component.newInstance("myWidget") で作成されるようになり、 myAction.doSomethingWithObject() に渡されます。

バグの追跡を支援する目的で、 ポップアップウィンドウ内でクライアントとサーバ間を行ったり来たりするすべてのパケットの内容を表示するデバッグモードを有効にすることができます。 デバッグモードを有効にするには、 次のいずれかを行います。 Javascript 内で setDebug() メソッドを実行する方法は次の通りです。


Seam.Remoting.setDebug(true);

components.xml を使って設定を行う方法は次のようになります。


<remoting:remoting debug="true"/>

デバッグ機能をオフにするには、 setDebug(false) を呼び出します。 独自のメッセージをデバッグログに書き込みたい場合は、 Seam.Remoting.log(message) を呼び出します。

When invoking a remote component method, it is possible to specify an exception handler which will process the response in the event of an exception during component invocation. To specify an exception handler function, include a reference to it after the callback parameter in your JavaScript:

var callback = function(result) { alert(result); };
var exceptionHandler = function(ex) { alert("An exception occurred: " + ex.getMessage()); };
Seam.Component.getInstance("helloAction").sayHello(name, callback, exceptionHandler);

If you do not have a callback handler defined, you must specify null in its place:

var exceptionHandler = function(ex) { alert("An exception occurred: " + ex.getMessage()); };
Seam.Component.getInstance("helloAction").sayHello(name, null, exceptionHandler);

The exception object that is passed to the exception handler exposes one method, getMessage() that returns the exception message which is produced by the exception thrown by the @WebRemote method.

画面の上部右端にデフォルトで表示されるローディングメッセージは、 変更、 レンダリングのカスタマイズ、 完全にオフにするなどが可能です。

リモートメソッドが実行されると、 その結果はクライアントに返される XML レスポンスにシリアライズされます。 この応答は次にクライアントにより Javascript オブジェクトにアンマーシャルされます。 他のオブジェクトへの参照を含む複雑なタイプの場合 (Javabeans など)、 こうした参照されるオブジェクトもすべて応答の一部としてシリアライズされます。 これらのオブジェクトは他のオブジェクトを参照することができ、 またこの他のオブジェクトはその他のオブジェクトを参照できるといった具合になります。 チェックしないままにしておくと、 このオブジェクト「グラフ」はオブジェクト間で存在する関係によっては非常に膨大なものになる可能性があります。 派生的な問題として (応答が冗長となる問題とは別)、 クライアントに対して機密情報が公開されてしまうのを防ぎたい場合もあるかもしれません。

Seam Remoting は、 リモートメソッドの @WebRemote アノテーションの exclude フィールドを指定することでそのオブジェクトグラフを「制約する」シンプルな方法を提供しています。 このフィールドはドット (「.」) 表記を使って指定されるパスまたは複数のパスを含む String 配列を受け取ります。 リモートメソッドを呼び出すと、 これらのパスと一致する結果となるオブジェクトグラフ内のオブジェクトがシリアライズされる結果パケットから除外されます。

すべての例で次の Widget クラスを使用します。

@Name("widget")

public class Widget
{
  private String value;
  private String secret;
  private Widget child;
  private Map<String,Widget> widgetMap;
  private List<Widget> widgetList;
  
  // getters and setters for all fields
}

By default there is no active transaction during a remoting request, so if you wish to perform database updates during a remoting request, you need to annotate the @WebRemote method with @Transactional, like so:

  @WebRemote @Transactional(TransactionPropagationType.REQUIRED)
  public void updateOrder(Order order) {
    entityManager.merge(order);
  }

Seam Remoting は JMS メッセージングに対して実験的にサポートを提供しています。 本セクションでは現在実装されている JMS サポートについて記載していますが、 今後、 変更される可能性があるので注意してください。 現在、 この機能を実稼働環境下で使用することは推奨されていません。

ポーリングの発生方法を制御するために変更できるパラメータが二つあります。 一つ目は Seam.Remoting.pollInterval で、 新しいメッセージに対して後続ポールが発生する間隔を制御します。 秒単位で表現します、 デフォルト設定は 10 になります。

二つ目のパラメータは Seam.Remoting.pollTimeout で、 このパラメータも秒単位で表現されます。 サーバへの要求がタイムアウトして空白の応答を送信するまでの新しいメッセージを待機する長さを制御します。 デフォルトは 0秒で、 サーバがポールされると配信できるメッセージがない場合は空白の応答が直ちに返されます。

pollTimeout 値を高く設定する場合は注意が必要です。 各リクエストがメッセージを待機する必要があるということは、 メッセージが受信されるまでまたはその要求がタイムアウトするまでサーバスレッドが固定されるということになります。 こうした要求が同時に多数発生すると、 大量のスレッドが固定される結果になります。

これらのオプションは components.xml 経由で設定することを推奨しますが、 必要に応じて Javascript で上書きすることができます。 次の例ではポーリングがかなりアグレッシブに発生するよう設定する方法を示しています。 これらのパラメータはご使用のアプリケーションに適切な値を設定してください。

components.xml:


<remoting:remoting poll-timeout="5" poll-interval="1"/>

JavaScript:


// Only wait 1 second between receiving a poll response and sending the next poll request.
Seam.Remoting.pollInterval = 1;
  
// Wait up to 5 seconds on the server for new messages
Seam.Remoting.pollTimeout = 5;