第7章 リモーティング

Seam は、AJAX (Asynchronous Javascript and XML : 非同期 Javascript と XML) を利用した WEB ページからリモートアクセスするコンポーネントの便利なメソッドを提供します。 この機能のためのフレームワークは、事前の開発をほとんどすることなしに提供されます。 コンポーネントに必要なものは、AJAX を通してアクセス可能とするための簡単なアノテーションだけです。 この章は、AJAX 可能な WEB ページを構築するために必要なステップを記述しています。 続いて、Seam リモーティングフレームワークをさらに詳しく説明します。

7.1. 設定

リモーティングを使用するために、 最初に、Seam リモーティングサーブレットを web.xml ファイルに設定しなければなりません。

          
  <servlet>
    <servlet-name>Seam Remoting</servlet-name>
    <servlet-class>org.jboss.seam.remoting.SeamRemotingServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Seam Remoting</servlet-name>
    <url-pattern>/seam/remoting/*</url-pattern>
  </servlet-mapping>
        
        

次のステップは、WEB ページに必要な Javascript をインポートすることです。 インポートしなければならないスクリプトは、最低 2 つあります。 最初の 1 つは、リモーティング機能を可能とするすべてのクライアントサイドフレームワークのコードが含まれています。

          
  <script type="text/javascript" src="seam/remoting/resource/remote.js">
    <!--
    // This space intentionally left blank
    //-->
  </script>
            
        

2 つめのスクリプトは、あなたが呼び出したいコンポーネントのスタブとタイプ定義が含まれています。 それは、コンポーネントのローカルインタフェースに基づいて動的に生成され、 インタフェースのリモートメソッドを呼び出すために使用可能なすべてのクラスのタイプ定義を含みます。 スクリプトの名前は、コンポーネントの名前を表しています。 例えば、 ステートレスセッション Bean が、@Name("customerAction") アノテーションを持つ場合、 スクリプトのタグは以下のようになるはずです。

          
  <script type="text/javascript" src="seam/remoting/interface.js?customerAction">
    <!--
    // This space intentionally left blank
    //-->
  </script>
        
        

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

          
  <script type="text/javascript" src="seam/remoting/interface.js?customerAction&accountAction">
    <!--
    // This space intentionally left blank
    //-->
  </script>
        
        

7.2. "Seam" オブジェクト

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

7.2.1. Hello World サンプル

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

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

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

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

書く必要があるサーバサイドのコードはこれだけです。 それでは、WEB ページのために - 新しいページを作成して、 以下のスクリプトをインポートしましょう。

          
  <script type="text/javascript" src="seam/remoting/resource/remote.js">
    <!--
    // This space intentionally left blank
    //-->
  </script>

  <script type="text/javascript" src="seam/remoting/interface.js?helloAction">
    <!--
    // This space intentionally left blank
    //-->
  </script>
          
        

十分にインタラクティブなユーザエクスペリエンス (user experience) とするために、ページにボタンを付けましょう。

          
  <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 メッセージを表示します。 少し時間を節約したい場合、 この Hello World サンプルソースコードすべては、 Seam の /examples/remoting/helloworld ディレクトリにあります。

ところで、このスクリプトのコードは何をするのでしょうか。 小さな部品に分解しましょう。 手始めに、2 つのメソッドを実装した Javascript コードから理解することも可能です。 最初のメソッドは、名前入力のユーザプロンプトに責務を持ち、リモートリクエストを行います。 以下の行から見てみましょう。

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

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

コード行全体で行っていることは、コンポーネントの sayHello メソッドの呼び出しと、 パラメータとして name を渡すことです。 2 番目のパラメータ sayHelloCallback は、 このコンポーネントの sayHello メソッドのパラメータではありません。 その代わり、Seam リモーティングフレームワークに、リクエストへのレスポンスを受けたら、 それを sayHelloCallback メソッドに渡すべきことを指示します。 このコールバックパラメータは、まったくオプションです。 そこで、返り値 void のメソッドを呼び出す場合、 あるいは、結果を気にしない場合、 遠慮なくそのままにしてください。

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

7.2.2. Seam.Component

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

7.2.2.1. Seam.Component.newInstance()

新しいエンティティ、あるいは、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";
          

7.2.2.2. Seam.Component.getInstance()

getInstance() メソッドは、 Seam セッション Bean コンポーネントのスタブの参照を取得するために使用されます。 それは、コンポーネントに対してリモートのメソッド実行に使用可能です。 このメソッドは、特定のコンポーネントのシングルトンを返します。 その結果、続けて同じコンポーネント名で 2 回呼び出すと、同じコンポーネントインスタンスが返されます。

前記のサンプルから続けて、 新しい customer を生成、保存しようとする場合、 customerAction コンポーネントの saveCustomer() メソッドにそれを渡します。

  Seam.Component.getInstance("customerAction").saveCustomer(customer);          
          

7.2.2.3. Seam.Component.getComponentName()

それがコンポーネントの場合、 オブジェクトをこのメソッドに渡すとコンポーネント名を返します。 また、コンポーネントでない場合、null を返します。

  if (Seam.Component.getComponentName(instance) == "customer")
    alert("Customer");
  else if (Seam.Component.getComponentName(instance) == "staff")
    alert("Staff member");          
          

7.2.3. Seam.Remoting

Seam リモーティングのための大部分のクライアントサイド機能は、 Seam.Remoting オブジェクトに含まれます。 メソッドを直接呼ぶ必要はほとんどないとはいえ、 言及する価値のある重要なものがあります。

7.2.3.1. Seam.Remoting.createType()

アプリケーションが Seam コンポーネントではない JavaBean を含むか、あるいは、使う場合、 パラメータをコンポーネントメソッドに渡すために、 クライアントサイドでこれらのタイプを作成する必要があるかもしれません。 必要なタイプのインスタンスを作成するために、 createType() メソッドを使用してください。 パラメータとして、完全修飾の Java クラスを渡してください。

  var widget = Seam.Remoting.createType("com.acme.widgets.MyWidget");          
          

7.2.3.2. Seam.Remoting.getTypeName()

このメソッドは、non-component タイプ用であることを別にすれば、Seam.Component.getComponentName() と等価です。 オブジェクトインスタンスにタイプの名前を返します。 また、タイプが既知でない場合、null を返します。 この名前は、タイプの Java クラス完全修飾名です。

7.3. クライアント インタフェース

上記、設定の章では、 インタフェース、あるいは、コンポーネントのための "stub" は、 seam/remoting/interface.js を通じてページにインポートされます。

        
  <script type="text/javascript" src="seam/remoting/interface.js?customerAction">
    <!--
    // This space intentionally left blank
    //-->
  </script>
        
      

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

2 つのタイプの生成可能なクライアントスタブがあります。"実行可能" スタブ、そして、"タイプ" スタブです。 実行可能スタブは、振る舞いを持ち、セッション Bean コンポーネントに対するメソッドを実行するために使用されます。 一方、タイプスタブは、状態を保持し、パラメータあるいは結果として返り値として送付可能なタイプを表します。

クライアントスタブのタイプは、Seam コンポーネントに応じて生成されます。 コンポーネントがセッション Bean の場合、 そのときは実行可能スタブが生成され、 それ以外の、エンティティ、また、JavaBeanの場合、 タイプスタブが生成されます。 この規則には 1 つの例外があります。 コンポーネントが JavaBean (つまり、セッション Bean や エンティティ Bean でない場合)であり、 そして、そのメソッドに@WebRemote アノテーションが付けられた場合、 タイプスタブの代わりに、実行可能スタブが生成されます。 これは、セッション Bean にアクセスできない non-EJB 環境で、 JavaBean コンポーネントのメソッドを呼び出すリモーティングを使用可能にします。

7.4. コンテキスト

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

7.4.1. 対話 ID の設定と読み込み

対話スコープでリモート呼び出しをしようとする場合、 Seam リモーティングコンテキスト内にある対話 ID の読み込みと設定が可能である必要があります。 リモートリクエストの後に対話 ID を読み込むためには、 Seam.Remoting.getContext().getConversationId() を呼び出します。 リクエストの前に対話 ID を設定するためには、 Seam.Remoting.getContext().setConversationId() を呼び出します。

対話 ID が明示的に Seam.Remoting.getContext().setConversationId() で設定されない場合、 リモート呼び出しによって返される最初の有効な対話 ID が自動的に割り当てられます。 ページ内に複数の対話 ID を使用する場合、 それぞれの呼び出しの前に対話 ID を明示的に設定する必要があるかもしれません。 1 つの対話だけを使用する場合、 特別なことをする必要はありません。

7.5. バッチリクエスト

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

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

startBatch() メソッドを通して新たなバッチを起動したけれど、それを送りたくないと決めた場合、 Seam.Remoting.cancelBatch() メソッドは、キューイングあるいはバッチモードを終了したすべての呼び出しを破棄します。

使用されたバッチのサンプルは、/examples/remoting/chatroom を参照ください。

7.6. データタイプでの動作

7.6.1. プリミティブ型 / 基本タイプ

この章は、基本データタイプのサポートを記述しています。 サーバサイドでは、 これらの値は、一般的にプリミティブタイプ、あるいは、一致するラッパクラスと互換性があります。

7.6.1.1. String 型

String パラメータ値を設定する場合、 単純に Javascript String オブジェクトを使用してください。

7.6.1.2. Number 型

Java でサポートされているすべての数字タイプはサポートされています。 クライアントサイドでは、 数値は、常に String 表現としてシリアライズされています。 そして、サーバサイドでは、これらは、正しい目的のタイプに変換されます。 プリミティブ、または、ラッパタイプへの変換は、 ByteDoubleFloatIntegerLong、 そして、Short タイプをサポートします。

7.6.1.3. Boolean 型

Boolean は、クライアントサイドでは Javascript Boolean value で表現され、 サーバサイドでは Java boolean で表現されます。

7.6.2. JavaBean

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

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

  @Name("myAction")
  public class MyAction implements MyActionLocal {
    public void doSomethingWithObject(Object obj) {
      // code
    }
  }
        

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

                  
  <script type="text/javascript" src="seam/remoting/interface.js?myAction&myWidget">
    <!--
    // This space intentionally left blank
    //-->
  </script>        
          
        

これは、myWidget オブジェクトが Seam.Component.newInstance("myWidget") で生成されることを可能にしています。 それは、myAction.doSomethingWithObject() に渡されることが可能です。

7.6.3. Date 型と Time 型

Date 値は、厳密にミリ秒の String 表現にシリアライズされます。 クライアントサイドでは、date 値と連携させるためにJavascript Data オブジェクトを使用します。 サーバサイドでは、 java.util.Date (あるいは、java.sql.Date または java.sql.Timestamp のような派生クラス) が使用されます。

7.6.4. Enum 型

When setting the value for an enum parameter, simply use the String representation of the enum. Take the following component as an example: クライアントサイドでは、 enum は、String と同様に扱われます。 enum パラメータに値を設定する場合、 単純に、enum の String 表現を使用しましょう。 例として以下のコンポーネントを見てみましょう。

  @Name("paintAction")
  public class paintAction implements paintLocal {
    public enum Color {red, green, blue, yellow, orange, purple};

    public void paint(Color color) {
      // code
    }    
  }            
        

paint() メソッドを red で呼び出すためには、 String リテラルとしてパラメータ値を渡してください。

  Seam.Component.getInstance("paintAction").paint("red");
        

この逆も正しいです。 つまり、コンポーネントメソッドが enum パラメータを返す場合 (あるいは、返されるオブジェクトグラフのどこかに enum フィールドが含まれる場合) クライアントサイドでは、それは String として表現されます。

7.6.5. コレクション

7.6.5.1. Bag 型

Bag は、配列、collection、list、set (Map を除く - この章の次項を参照してください) を含むすべてのコレクションタイプを網羅しており、 Javascript 配列 としてクライアントサイドで実装されています。 パラメータとしてそれらのタイプの 1 つを受けるコンポーネントメソッドを呼ぶとき、 パラメータは、Javascript 配列 でなけれななりません。 コンポーネントメソッドがこれらタイプの 1 つを返す場合、 返り値も Javascript 配列 になるでしょう。 フレームワークのリモート呼び出しは、 サーバサイドにおいて bag をコンポーネントメソッド呼び出しのための適切なタイプに変換するほど気が利いています。

7.6.5.2. Map 型

Javascript では Map はもともとサポートされていないため、 簡単な Map 実装が、Seam リモーティングフレームワークで提供されます。 リモート呼び出しのためのパラメータとして Map を生成するために、 新しい Seam.Remoting.Map オブジェクトを生成しましょう。

  var map = new Seam.Remoting.Map();          
          

この Javascript 実装は、Map と連携して基本的なメソッドを提供します。 ( size()isEmpty()keySet()values()get(key)put(key, value)remove(key) そして、contains(key) ) これらのメソッドのそれぞれは、Java の対応メソッドと等価です。 例えば、keySet()values() などメソッドがコレクションを返す場合、 返される Javascript Array オブジェクトは、Key とオブジェクトをそれぞれ含みます。

7.7. デバッグ

バグ追跡を支援するために、デバッグモードが可能です。 デバッグモードでは、クライアントとサーバの間で行きかうすべてのパケットの内容を、 ポップアップウインドウに表示するデバッグが可能です。 デバックモードを有効にするためには、 setDebug() メソッドを実行します。

  Seam.Remoting.setDebug(true);      
      

デバッグを停止するには、setDebug(false) を呼びます。 デバッグログに独自のメッセージを書きたい場合、 Seam.Remoting.log(message) を呼びます。

7.8. メッセージの読み込み

スクリーンの右上の隅に表示されるデフォルトの読み込みメッセージは、 変更可能で、カスタマイズされたメッセージのレンダリングや、あるいは、完全に停止することも可能です。

7.8.1. メッセージの変更

メッセージをデフォルトの "Please Wait..." から 異なるものに変更するには、 Seam.Remoting.loadingMessage の値を設定します。

  Seam.Remoting.loadingMessage = "Loading...";        
        

7.8.2. 読み込みメッセージの非表示

読み込みメッセージの表示を完全に停止させるに、 displayLoadingMessage() または、hideLoadingMessage() 実装を何もしないようにオーバーライドします。

  // don't display the loading indicator
  Seam.Remoting.displayLoadingMessage = function() {};
  Seam.Remoting.hideLoadingMessage = function() {};        
        

7.8.3. カスタム読み込みインディケータ

読み込みインディケータをアニメーションアイコンを表示するようにオーバーライドすることも可能です。 あるいは、お望みのほかの何かにすることも可能です。 displayLoadingMessage() そして、hideLoadingMessage() メッセージをあなた独自の実装にオーバーライドしてください。

  Seam.Remoting.displayLoadingMessage = function() {
    // Write code here to display the indicator
  };
  
  Seam.Remoting.hideLoadingMessage = function() {
    // Write code here to hide the indicator
  };
        

7.9. JMS メッセージング

Seam リモーティングは、JMS メッセージングの実験的サポートを提供します。 この章では、現在、実装された JMS サポートを記述しています。 しかし、これは将来変更されるかもしれないことに注意してください。 この機能が本番環境で使用されることは現段階ではお勧めできません。

7.9.1. 設定

JMS Topic にサブスクライブする前に、 最初に、Seam リモーティングによりサブスクライブ可能な topic のリストを設定しなければなりません。 seam.propertiesweb.xml または、components.xml 中の org.jboss.seam.remoting.messaging.subscriptionRegistry.allowedTopics に基づいて topic をリストしてください。

<component name="org.jboss.seam.remoting.messaging.subscriptionRegistry">
    <property name="allowedTopics">chatroomTopic, stockTickerTopic</property>
</component>

7.9.2. JMS Topic のサブスクライブ

以下のサンプルは、JMS Topic のサブスクライブの方法を示しています。

  function subscriptionCallback(message)
  {
    if (message instanceof Seam.Remoting.TextMessage)
      alert("Received message: " + message.getText());
  }        
        
  Seam.Remoting.subscribe("topicName", subscriptionCallback);
        

Seam.Remoting.subscribe() メソッドは、2 つのパラメータを受けます。 最初は、JMS Topic にサブスクライブする名前、2 番目は、メッセージを受信したときに呼び出すコールバック関数です。

サポートされた 2 つのタイプのメッセージがあります。Text メッセージと Object メッセージです。 コールバック関数に渡されたメッセージのタイプをテストする必要がある場合、 instanceof オペレータを使って メッセージが Seam.Remoting.TextMessage または、Seam.Remoting.ObjectMessage であるかどうかをテストできます。 TextMessage は、 テキスト値をその text フィールドに含んでいます。 (代替として、getText() を呼んでください。) 一方、ObjectMessage は、オブジェクト値を object フィールドに含んでいます。 (代替として、getObject() メソッドを呼んでください。)

7.9.3. Topic からのアンサブスクライブ

Topic からのアンサブスクライブのためには、 Seam.Remoting.unsubscribe() を呼び出し、Topic 名を渡します。

  Seam.Remoting.unsubscribe("topicName");        
        

7.9.4. ポーリングプロセスのチューニング

どのようにポーリングを発生するかを管理する変更可能な 2 つのパラメータがあります。 最初の 1 つは、Seam.Remoting.pollIntervalです。 これは、どれ位の期間新たなメッセージのためにその後のポーリングを待つかを管理します。 このパラメータは、秒単位で表され、デフォルトは、10 です。

2 番目のパラメータは、 Seam.Remoting.pollTimeout で、 これも、秒単位で表されます。 これは、タイムアウトして空レスポンスを送信する前に、 どれ位の期間サーバへのリクエストが新しいメッセージを待つべきであるかを管理します。 そのデフォルトは、0 秒です。 これは、サーバがポーリングされたとき、 配信準備可能なメッセージがない場合、 空レスポンスが即座に返されます。

以下のサンプルは、 さらに積極的にポーリングを発生させるための設定方法を実演しています。 アプリケーションのために、 これらのパラメータは、適切な値に設定すべきです。

  // 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;