SeamFramework.orgCommunity Documentation

第24章 Webサービス

24.1. 設定とパッケージング
24.2. 対話型Webサービス
24.2.1. 推奨される方法
24.3. Webサービスの例
24.4. RESTEasy によるRESTful HTTP Webサービス
24.4.1. RESTEasy の設定と要求
24.4.2. Seam コンポーネントとしてのリソースとプロバイダ

SeamとJBossWSを統合することで、標準のJEE のWebサービスにたいして、対話型Webサービスにも対応したSeamのコンテキストフレームワークを十分に活用することができます。本章では、Seam環境でWebサービスが動作するのに必要な手順を説明します。

Webサービス要求をSeamがインタセプトできるように、必要なSeamのコンテキストがその要求に合わせて生成できなければなりませんが、それにはSOAPハンドラを特別に設定する必要があります。org.jboss.seam.webservice.SOAPRequestHandlerSOAPHandler実装として、Webサービス要求のスコープ中にSeamのライフサイクルを管理するのに使われています。

特殊な設定ファイルであるstandard-jaxws-endpoint-config.xmlは、Webサービスクラスを含むjarファイルのMETA-INFディレクトリに配置する必要があります。このファイルには、以下のようなSOAPハンドラの設定が含まれています。


<jaxws-config xmlns="urn:jboss:jaxws-config:2.0" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xmlns:javaee="http://java.sun.com/xml/ns/javaee"
              xsi:schemaLocation="urn:jboss:jaxws-config:2.0 jaxws-config_2_0.xsd">
   <endpoint-config>
      <config-name
>Seam WebService Endpoint</config-name>
      <pre-handler-chains>
         <javaee:handler-chain>
            <javaee:protocol-bindings
>##SOAP11_HTTP</javaee:protocol-bindings>
            <javaee:handler>
               <javaee:handler-name
>SOAP Request Handler</javaee:handler-name>
               <javaee:handler-class
>org.jboss.seam.webservice.SOAPRequestHandler</javaee:handler-class>
            </javaee:handler>
         </javaee:handler-chain>
      </pre-handler-chains>
   </endpoint-config>
</jaxws-config
>

では、Webサービス要求間でどのように対話が伝播されているのでしょう? Seamでは、SOAP要求と応答メッセージの両方でSOAPヘッダー要素を使い、そのconversation IDをコンシューマからサービスへ、またサービスからコンシューマへと伝えています。以下はconversation IDを含むWebサービスの要求の一例です。


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:seam="http://seambay.example.seam.jboss.org/">
  <soapenv:Header>
    <seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
  </soapenv:Header>
  <soapenv:Body>
    <seam:confirmAuction/>
  </soapenv:Body>
</soapenv:Envelope
>    
    

上記のSOAPメッセージで見られるように、SOAPヘッダー内にその要求のためのconversation ID(ここでは2)を持つconversationId要素があります。残念ながら、Webサービスを使うクライアントは多種多様で、なおかつさまざまな言語で記述されているため、ひとつの対話のスコープ内で使われると想定されるconversation IDの伝播をどのように実装するかは、個々のWebサービス間の開発者次第です。

ここで重要なのは、 conversationIdヘッダー要素はhttp://www.jboss.org/seam/webserviceの名前空間に適したものでなければいけません。そうでなければ、Seamはその要求からconversation IDを読み取ることができなくなってしまいます。上記の要求メッセージに対する応答の一例を以下に示します。


<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
  <env:Header>
    <seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
  </env:Header>
  <env:Body>
    <confirmAuctionResponse xmlns="http://seambay.example.seam.jboss.org/"/>
  </env:Body>
</env:Envelope
>    
    

ここにあるように、応答メッセージには要求と同じconversationId要素が含まれています。

Webサービスの一例を見てみましょう。ここで例示するコードは、すべてSeamの/examplesディレクトリにあるseamBayの例から引用したもので、前節で述べた推奨される方法に添っています。まず、Webサービスのクラスとそのメソッドから見てみましょう。

@Stateless

@WebService(name = "AuctionService", serviceName = "AuctionService")
public class AuctionService implements AuctionServiceRemote
{
   @WebMethod
   public boolean login(String username, String password)
   {
      Identity.instance().setUsername(username);
      Identity.instance().setPassword(password);
      Identity.instance().login();
      return Identity.instance().isLoggedIn();
   }
   // snip
}

ここで、WebサービスはステートレスセッションBeanで、JSR-181で定義されている通り、javax.jwsパッケージのJWSアノテーションを使ってアノテートされています。@WebServiceアノテーションは、このクラスがWebサービスを実装していることをコンテナに伝えます。そして、 login()メソッドの@WebMethodアノテーションがWebサービスとしてのメソッドを定義しています。@WebServiceアノテーションのnameとserviceName属性はオプションです。

仕様書にある通り、Webサービスのメソッドとして指定された各メソッドは、そのWebサービスのクラスのリモートインタフェース中でも宣言しておく必要があります。(WebサービスがステートレスセッションBeanの場合) 上記の例では、AuctionServiceRemoteインタフェースが@WebMethodとしてアノテートされているため、login()メソッドを宣言しなければなりません。

上記のコードにあるように、Webサービスが実装するlogin()メソッドは、Seamの組み込みIdentityコンポーネントに委譲されています。前節で推奨した方法を踏まえると、単にファサードとして記述されたWebサービスは、実際の作業をSeamコンポーネントに流します。これによって、Webサービスとクライアント間でビジネスロジックを最大限に再利用できます。

もう一つの例を見てみましょう。このWebサービスメソッドは、AuctionAction.createAuction()メソッドに委譲されることで新しい対話が始まります。

   @WebMethod

   public void createAuction(String title, String description, int categoryId)
   {
      AuctionAction action = (AuctionAction) Component.getInstance(AuctionAction.class, true);
      action.createAuction();
      action.setDetails(title, description, categoryId);
   }

以下は、AuctionActionからのコードです。

   @Begin

   public void createAuction()
   {
      auction = new Auction();
      auction.setAccount(authenticatedAccount);
      auction.setStatus(Auction.STATUS_UNLISTED);        
      durationDays = DEFAULT_AUCTION_DURATION;
   }

これにより、Webサービスがファサードとして実際の作業を対話型Seamコンポーネントに委譲することで、長い対話を続けていることが判ります。

SeamはJAX-RS 仕様(JSR 311)にあるRESTEasyの実装を組み込んでいます。これをSeamアプリケーションのどこまで”深く”取り入れるかは、以下のように自分で設定することができます。

まず始めに、RESTEasyライブラリと jaxrs-api.jarを取得し、あなたのアプリケーションにある他のライブラリと一緒にデプロイします。さらに、インテグレーションライブラリjboss-seam-resteasy.jarをデプロイします。

起動時には、@javax.ws.rs.Pathでアノテートされた全クラスが自動的に検出され、HTTPリソースとして登録されます。Seamは組み込まれたSeamResourceServletを使って自動的にHTTP要求を処理します。リソースのURIは以下のようにビルドされます。

一例として、以下のリソース定義ではhttp://your.hostname/seam/resource/rest/customer/123というURIを使った、いかなるGET要求に対してもプレーンテキスト表示を返します。

@Path("/customer")

public class MyCustomerResource {
    @GET
    @Path("/{customerId}")
    @ProduceMime("text/plain")
    public String getCustomer(@PathParam("customerId") int id) {
         return ...;
    }
}

他に設定する必要はありません。つまり、以上のようにデフォルトで問題なければ、web.xmlや他の設定を変更する必要はありません。あるいは、自分のSeamアプリケーションのRESTEasyの設定を変えても良いでしょう。まず、resteasy名前空間をXML設定ファイルのヘッダーにインポートします。


<components
   xmlns="http://jboss.com/products/seam/components"
   xmlns:resteasy="http://jboss.com/products/seam/resteasy"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation=
     http://jboss.com/products/seam/resteasy
         http://jboss.com/products/seam/resteasy-2.1.xsd
     http://jboss.com/products/seam/components
         http://jboss.com/products/seam/components-2.1.xsd"
>

次に、先に述べたように/restプレフィックスを変更できます。


<resteasy:application-config resource-path-prefix="/restv1"/>

この場合、リソースへのフルベースパスは/seam/resource/restv1/{resource}になります。ここで@Pathの定義とマッピングは変更しません。これは通常HTTP APIのバージョニングに使われる、アプリケーション全体のスイッチです。

リソース中においてフルパスでマップしたい場合は、ベースパスのストリッピングを無効にすることができます。


<resteasy:application-config strip-seam-resource-path="false"/>

これにより、リソースのパスは、例えば@Path("/seam/resource/rest/customer")でマップされています。この場合、リソースクラスのマッピングは、特定のデプロイメントシナリオに制限されるので、この機能は無効にしないほうが良いでしょう。

デプロイされたすべての@javax.ws.rs.Pathリソースおよび@javax.ws.rs.ext.Providerクラスに対し、Seamはクラスパスをスキャンします。このスキャンを無効化し、マニュアルでクラスを設定することもできます。


<resteasy:application-config
     scan-providers="false"
     scan-resources="false"
     use-builtin-providers="true">

     <resteasy:resource-class-names>
         <value
>org.foo.MyCustomerResource</value>
         <value
>org.foo.MyOrderResource</value>
     </resteasy:resource-class-names>

     <resteasy:provider-class-names>
         <value
>org.foo.MyFancyProvider</value>
     </resteasy:provider-class-names>

 </resteasy:application-config
>

use-built-in-providersは、RESTEasy組み込みプロバイダを有効もしくは無効にします。(デフォルトでは有効) プレーンテキストやJSON、JAXBをそのままで整列化できるので、この機能は有効にしておくと良いでしょう。

最後に、メディアタイプと言語のURI拡張子を設定できます。


<resteasy:application-config>

    <resteasy:media-type-mappings>
       <key
>txt</key
><value
>text/plain</value>
    </resteasy:media-type-mappings>

    <resteasy:language-mappings>
       <key
>deutsch</key
><value
>de-DE</value>
    </resteasy:language-mappings>

</resteasy:application-config
>

この定義によって、.txt.deutsch というURIサフィックスに、付加的なAcceptおよびAccept-Languageのヘッダー値であるtext/plainde-DEをマップすることができます。

どのリソースやプロバイダのインスタンスも、デフォルトでRESTEasyで管理されています。つまり、あるリソースのクラスをRESTEasyがインスタンス化し、ある要求が処理された後に、そのクラスは破棄されます。これがデフォルトのJAX-RSのライフサイクルになっています。プロバイダに関しては、アプリケーション全体に対して一度インスタンス化され、効率的に、シングルトンとなり、またステートレスになるよう想定されています。

リソースやプロバイダをSeamコンポーネントとして記述することもできます。こうすることでSeamのより豊富なライフサイクル管理のほか、バイジェクションやセキュリティ等のインタセプションを利用することができます。リソースのクラスをSeamコンポーネントにするのは簡単で、以下のようにします。

@Name("customerResource")

@Path("/customer")
public class MyCustomerResource {
    @In
    CustomerDAO customerDAO;
    @GET
    @Path("/{customerId}")
    @ProduceMime("text/plain")
    public String getCustomer(@PathParam("customerId") int id) {
         return customerDAO.find(id).getName();
    }
}

これで、要求がサーバーに届くとcustomerResourceのインスタンスをSeamが処理するようになります。これはEVENT-スコープなSeam JavaBeanコンポーネントなので、デフォルトのJAX-RSライフサイクルと違いはありません。しかし、これですべてのSemaインジェクションやその他のSeamコンポーネントおよびコンテキストが利用できるようになります。現在、加えてSESSIONAPPLICATION、および STATELESSのリソースコンポーネントがサポートされています。サーバーサイドのセッションコンテキストを正しく処理するためには、どのHTTP要求も、有効なセッション識別子(クッキー、URIパスパラメータ)を送信する必要があります。

対話スコープのリソースコンポーネントと対話のマッピングは現在サポートされていませんが、間もなく利用できるようになる予定です。

プロバイダのクラスもまたSeamコンポーネントとして利用できますが、APPLICATION-scopedもしくはSTATELESSである必要があります。

リソースとプロバイダは他のSeamコンポーネント同様、EJBやJavaBeansとして使うことができます。