SeamFramework.orgCommunity Documentation
SeamとJBossWSを統合することで、標準のJEE のWebサービスにたいして、対話型Webサービスにも対応したSeamのコンテキストフレームワークを十分に活用することができます。本章では、Seam環境でWebサービスが動作するのに必要な手順を説明します。
Webサービス要求をSeamがインタセプトできるように、必要なSeamのコンテキストがその要求に合わせて生成できなければなりませんが、それにはSOAPハンドラを特別に設定する必要があります。org.jboss.seam.webservice.SOAPRequestHandler
はSOAPHandler
実装として、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 ブートストラップと設定をシームレスに組み込み、リソースとプロバイダを自動検出。
SeamResourceServlet によりHTTP/REST 要求を処理し、外部サーブレットやweb.xml での設定は不要。
Seam コンポーネントとしてリソースを記述することで、Seamのライフサイクル管理とインタセプション(バイジェクション)をすべて利用可能。
まず始めに、RESTEasyライブラリと jaxrs-api.jar
を取得し、あなたのアプリケーションにある他のライブラリと一緒にデプロイします。さらに、インテグレーションライブラリjboss-seam-resteasy.jar
をデプロイします。
起動時には、@javax.ws.rs.Path
でアノテートされた全クラスが自動的に検出され、HTTPリソースとして登録されます。Seamは組み込まれたSeamResourceServlet
を使って自動的にHTTP要求を処理します。リソースのURIは以下のようにビルドされます。
一般的な例に合わせて説明すると、URIはSeamResourceServlet
に対してweb.xml
内でマップしてあるパターン(例えば、/seam/resource
)で始まります。ベースが異なる場合、RESTful リソースが処理されるように、この設定を変更してください。この変更はグローバルなものなので、他のSeamリソース(例えば、s:graphicImage
)もそのベースパスで処理されることに注意してください。
次にSeamのRESTEasy の組み込みで、このベースパスに任意の文字列を追加します。デフォルトでは /rest
になっています。従って、この例の場合、リソースのフルベースパスは/seam/resource/rest
になります。アプリケーションで使う場合、例えば将来的にそのサービスのREST API をアップグレードすることを考慮して、バージョン番号を追加する等して(/seam/resource/restv1
)、この文字列は変えておいたほうが良いでしょう。(古いクライアントは元のURIベースを保持します)
これで、 実際のリソースは定義した@Path
以下で利用できるようになります。例えば、@Path("/customer")
でマップしたリソースは、/seam/resource/rest/customer
以下で利用できます。
一例として、以下のリソース定義では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/plain
とde-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コンポーネントおよびコンテキストが利用できるようになります。現在、加えてSESSION
、 APPLICATION
、および STATELESS
のリソースコンポーネントがサポートされています。サーバーサイドのセッションコンテキストを正しく処理するためには、どのHTTP要求も、有効なセッション識別子(クッキー、URIパスパラメータ)を送信する必要があります。
対話スコープのリソースコンポーネントと対話のマッピングは現在サポートされていませんが、間もなく利用できるようになる予定です。
プロバイダのクラスもまたSeamコンポーネントとして利用できますが、APPLICATION
-scopedもしくはSTATELESS
である必要があります。
リソースとプロバイダは他のSeamコンポーネント同様、EJBやJavaBeansとして使うことができます。