The Java EE 7 Tutorialの31 JAX-RS: Advanced Topics and an Exampleの章をテキトーに訳した。
31 JAX-RS: Advanced Topics and an Example
Java API for RESTful Web Services (JAX-RS, JSR 339で定義)は、開発者が容易にRESTアーキテクチャを使用できるように設計されています。この章ではJAX-RSの高度な機能について解説します。もしJAX-RSが初めてであれば、この章を読み進める前にChapter 29, "Building RESTful Web Services with JAX-RS"を参照してください。
JAX-RSは、CDI・EJB・Servletに統合されています。
この章では以下のトピックを扱います。
- Annotations for Field and Bean Properties of Resource Classes
- Validating Resource Data with Bean Validation
- Subresources and Runtime Resource Resolution
- Integrating JAX-RS with EJB Technology and CDI
- Conditional HTTP Requests
- Runtime Content Negotiation
- Using JAX-RS with JAXB
- The customer Example Application
31.1 Annotations for Field and Bean Properties of Resource Classes
リソースクラス用のJAX-RSアノテーションによって、URIやリクエストヘッダーの指定部分から値を抽出することができます。
JAX-RSが提供するアノテーションを表 31-1に示します。
アノテーション | 説明 |
---|---|
@Context | クラスフィールド、ビーンプロパティ、メソッド引数に情報をインジェクトします。 |
@CookieParam | クッキーリクエストヘッダーで宣言されているクッキーから情報を抽出します。 |
@FormParam | application/x-www-form-urlencoded コンテンツタイプを持つリクエスト表現から情報を抽出します。 |
@HeaderParam | ヘッダーの値を抽出します。 |
@PathParam | URIテンプレートパラメーターの値を抽出します。 |
@QueryParam | URIクエリパラメータの値を抽出します。 |
31.1.1 Extracting Path Parameters
URIパステンプレートはURIシンタックス内に埋め込まれた変数からなるURIです。メソッドを呼ぶときに、@PathParam
を使用してURIパスの変数を使うことができます。
以下のコードは従業員のe-mailアドレスが渡された時に従業員の名字を抽出する方法です。
@Path("/employees/{firstname}.{lastname}@{domain}.com") public class EmpResource { @GET @Produces("text/xml") public String getEmployeelastname(@PathParam("lastname") String lastName) { ... } }
この例では、@Path
がURI変数(もしくはパスパラメータ){firstname}
, {lastname}
, {domain}
を宣言しています。リクエストメソッド引数の@PathParam
がe-mailアドレスから名字を抽出します。
HTTPリクエストGET /employees/john.doe@example.com
を行うと、"dow"
が{lastname}
に入れられます。
URI変数に正規表現を宣言可能です。たとえば、名字が大小文字のみで構成するものだけを受け入れるようにするには、以下のような正規表現を宣言可能です。
@Path("/employees/{firstname}.{lastname[a-zA-Z]*}@{domain}.com")
名字が正規表現にマッチしない場合、404レスポンスが返されます。
31.1.2 Extracting Query Parameters
@QueryParam
を使用してリクエストURIのクエリコンポーネントからクエリパラメータを抽出できます。
たとえば、指定範囲内に入社した全従業員を問い合わせるために、以下のようなメソッドシグネチャを使用できます。
@Path("/employees/") @GET public Response getEmployees( @DefaultValue("2003") @QueryParam("minyear") int minyear, @DefaultValue("2013") @QueryParam("maxyear") int maxyear) {...}
このコードは二つのクエリパラメータ、minyear
とmaxyear
を定義しています。以下のHTTPリエクストで2003年から2013年に入社した全従業員を問い合わせます。
GET /employees?maxyear=2013&minyear=2003
@DefaultValue
はデフォルト値を定義しており、もしクエリパラメータとなる値が無ければこの値が使われます。デフォルトでは、JAX-RSは、object
にはnull、プリミティブデータ型にはゼロ、を割り当てます。nullやゼロを避けるために@DefaultValue
で任意のデフォルト値を設定可能です。
31.1.3 Extracting Form Data
HTMLフォームからパラメータを抽出するには@FormParam
を使用します。たとえば、以下のフォームには、氏名・住所・従業員のマネージャ名、を入力します。
<FORM action="http://example.com/employees/" method="post"> <p> <fieldset> Employee name: <INPUT type="text" name="empname" tabindex="1"> Employee address: <INPUT type="text" name="empaddress" tabindex="2"> Manager name: <INPUT type="text" name="managername" tabindex="3"> </fieldset> </p> </FORM>
HTMLフォームからマネージャ名を抽出するには以下のコードを使用します。
@POST @Consumes("application/x-www-form-urlencoded") public void post(@FormParam("managername") String managername) { // Store the value ... }
フォームパラメータの名前と値のマップを取得するには以下のようなコードを使用します。
@POST @Consumes("application/x-www-form-urlencoded") public void post(MultivaluedMap<String, String> formParams) { // Store the message }
31.1.4 Extracting the Java Type of a Request or Response
javax.ws.rs.core.Context
はリクエストやレスポンスに関するJavaの型を取得します。
javax.ws.rs.core.UriInfo
インタフェースはリクエストURIのコンポーネントに関する情報を提供します。以下のコードはクエリとパスパラメータのkey/valueマップを取得します。
@GET public String getParams(@Context UriInfo ui) { MultivaluedMap<String, String> queryParams = ui.getQueryParameters(); MultivaluedMap<String, String> pathParams = ui.getPathParameters(); }
javax.ws.rs.core.HttpHeaders
インタフェースはリクエストヘッダーとクッキーに関する情報を取得します。以下のコードはヘッダーとクッキーパラメータのkey/valueマップを取得します。
@GET public String getHeaders(@Context HttpHeaders hh) { MultivaluedMap<String, String> headerParams = hh.getRequestHeaders(); MultivaluedMap<String, Cookie> pathParams = hh.getCookies(); }
31.2 Validating Resource Data with Bean Validation
JAX-RSは、リソースクラスを検証するためのBean Validationをサポートしています。
31.2.1 Using Constraint Annotations on Resource Methods
Bean Validation制約アノテーションはリソースのパラメータに適用可能です。サーバはパラメータを検証し、通過させるかjavax.validation.ValidationException
例外をスローします。
@POST @Path("/createUser") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public void createUser(@NotNull @FormParam("username") String username, @NotNull @FormParam("firstName") String firstName, @NotNull @FormParam("lastName") String lastName, @Email @FormParam("email") String email) { ... }
この例では、ビルトインの制約@NotNull
をフォームフィールドのusername
, firstName
, lastName
に適用しています。ユーザ定義の@Email
制約はemail
フォームフィールドのemailアドレスが正しいフォーマットかどうかを検証します。
また、制約はリソースクラス内のフィールドにも適用可能です。
@Path("/createUser") public class CreateUserResource { @NotNull @FormParam("username") private String username; @NotNull @FormParam("firstName") private String firstName; @NotNull @FormParam("lastName") private String lastName; @Email @FormParam("email") private String email; ... }
この例では、一つ前のサンプルではメソッド引数に適用されていた制約と、同様のものがクラスフィールドに適用されています。両者の振る舞いは同じものになります。
また、getterメソッドに制約アノテーションを付与することで、制約をリソースクラスのJavaBeansプロパティにも適用可能です。
@Path("/createuser") public class CreateUserResource { private String username; @FormParam("username") public void setUsername(String username) { this.username = username; } @NotNull public String getUsername() { return username; } ... }
また、制約はリソースのクラスレベルにも適用可能です。以下の例では、ユーザ定義制約@PhoneRequired
が、ユーザが少なくとも一つの電話番号を入力していることを保証します。homePhone
もしくはmobilePhone
のどちらかがnullになることはあっても、両方ともnullにはなりません。
@Path("/createUser") @PhoneRequired public class CreateUserResource { @FormParam("homePhone") private Phone homePhone; @FormParam("mobilePhone") private Phone mobilePhone; ... }
31.2.2 Validating Entity Data
validation制約アノテーションを含むクラスはリソースクラスのメソッド引数で使用可能です。エンティティクラスを検証するには、メソッド引数に@Valid
を付与します。たとえば、以下のクラスは標準とユーザ定義validation制約の両方を含むユーザ定義クラスです。
@PhoneRequired public class User { @NotNull private String username; private Phone homePhone; private Phone mobilePhone; ... }
エンティティクラスはリソースメソッドの引数として使用可能です。
@Path("/createUser") public class CreateUserResource { ... @POST @Consumers(MediaType.APPLICATION_XML) public void createUser(@Valid User user) { ... } ... }
@Valid
はエンティテクラスが実行時に妥当であることを保証します。また、ユーザ定義制約をエンティテイvalidationに追加することも出来ます。
@Path("/createUser") public class CreateUserResource { ... @POST @Consumers(MediaType.APPLICATION_XML) public void createUser(@ActiveUser User user) { ... } ... }
この例では、ユーザ定義の@ActiveUser
制約が、エンティティクラス内に定義された@PhoneRequired
と@NotNull
に加えて、User
クラスに適用されます。
リソースメソッドがエンティティクラスを返す場合、validationは@Valid
を適用するかリソースメソッドにユーザ定義の制約アノテーションを付与することで実行されます。
@Path("/getUser") public class GetUserResource { ... @GET @Path("{username}") @Produces(MediaType.APPLICATION_XML) @ActiveUser @Valid public User getUser(@PathParam("username") String username) { // find the User return user; } ... }
この例では、@ActiveUser
制約が、エンティティクラス内に定義された@PhoneRequired
と@NotNull
制約と同様に、戻り値のクラスに適用されます。
31.2.3 Validation Exception Handling and Response Codes
javax.validation.ValidationException
やConstraintValidationException
を除くValidationException
のサブクラスがスローされる場合、JAX-RSランタイムはクライアントリクエストに対しHTTPステータスコード500 (Internal Server Error)を返します。
ConstraintValidationException
がスローされる場合、JAX-RSランタイムは以下のHTTPステータスコードの内いずれか一つをクライアントに返します。
- 500 (Internal Server Error) メソッドの戻り値型を検証中に例外がスローされた場合。
- 400 (Bad Request) 上記以外の全ケース。
31.3 Subresources and Runtime Resource Resolution
URIリクエストの一部としてのみ処理するリソースクラスを使用し、URIパスの残りを処理するサブリソースをルートリソースで実装することが可能です。
@Path
を付与したリソースクラスのメソッドは、サブリソースメソッドかサブリソースロケーターのどちらかになれます。
- サブリソースメソッドは、対応するリソースのサブリソースのリクエストを処理するために使用します。
- サブリソースロケーターは、対応するリソースのサブリソースの場所を示すために使用します。
31.3.1 Subresource Methods
サブリソースメソッド(subresource method)はHTTPリクエストを直接処理します。メソッドは、@GET
や@POST
などのrequest method designatorと、@Path
を付与しなければなりません。リソースクラスとメソッドのURIテンプレートを繋げ、それによって作成されたURIテンプレートとリクエストURIがマッチする場合、メソッドが呼ばれます。
以下のコードは、従業員のemailアドレスが渡されたとき、従業員の名字を抽出するためにサブリソースメソッドを使用している例です。
@Path("/employeeinfo") public class EmployeeInfo { public employeeinfo() {} @GET @Path("/employees/{firstname}.{lastname}@{domain}.com") @Produces("text/xml") public String getEmployeeLastName(@PathParam("lastname") String lastName) { ... } }
getEmployeeLastName
メソッドは以下のGET
リクエストでdoe
を返します。
GET /employeeinfo/employees/john.doe@example.com
31.3.2 Subresource Locators
サブリソースロケーター(subresource locator)は、HTTPリクエストを処理するオブジェクトを返します。メソッドにはrequest method designatorを付与する必要はありません。サブリソース内でサブリソースロケーターを宣言する必要があり、サブリソースロケーターのみ実行時のリソース解決に使用されます。
以下のコードがサブリソースロケーターの例です。
// Root resource class @Path("/employeeinfo") public class EmployeeInfo { // Subresource locator: obtains the subresource Employee // from the path /employeeinfo/employees/{empid} @Path("/employees/{empid}") public Employee getEmployee(@PathParam("empid") String id) { // Find the Employee based on the id path parameter Employee emp = ...; ... return emp; } }
// Subresource class public class Employee { // Subresource method: returns the employee's last name @GET @Path("/lastname") public String getEmployeeLastName() { ... return lastName; } }
このコードでは、getEmployee
メソッドがEmployee
を提供するサブリソースロケーターで、lastname
用のサービスリクエストを持ちます。
HTTPリクエストがGET /employeeinfo/employees/as209/
の場合、getEmployee
はas209
を持つEmployee
を返します。実行時に、JAX-RSはGET /employeeinfo/employees/as209/lastname
リクエストをgetEmployeeLastName
に送ります。getEmployeeLastName
はemployeeの名字にas209
を持つ値を返します。
31.4 Integrating JAX-RS with EJB Technology and CDI
一般的に、JAX-RSとEJBを組み合わせるには、beanクラスに@Path
を付与してルートリソースクラスにする必要があります。@Path
をstateless session beanやシングルトンビーンに付与可能です。
以下のコードはstateless session beanやシングルトンビーンをJAX-RSのルートリソースクラスにしています。
@Stateless @Path("stateless-bean") public class StatelessResource {...}
@Singleton @Path("singleton-bean") public class SingletonResource {...}
また、session beanはサブリソースにもできます。
JAX-RSとCDIはコンポーネントモデルが若干異なります。デフォルトでは、JAX-RSのルートリソースクラスはリクエストスコープで管理されており、スコープを指定するアノテーションは必要とされません。@RequestScoped
や@ApplicationScoped
を付与したCDIのマネージドビーンはJAX-RSのリソースクラスにできます。
以下のコードはJAX-RSのリソースクラスの例です。
@Path("/employee/{id}") public class Employee { public Employee(@PathParam("id") String id) {...} }
@Path("{lastname}") public final class EmpDetails {...}
以下のコードはJAX-RSリソースクラスをCDIビーンにした例です。ビーンはproxyable*1でなければならないので、Employee
は非privateで引数無しのコンストラクタを持つ必要があり、EmpDetails
は非final
でなければなりません。
@Path("/employee/{id}") @RequestScoped public class Employee { public Employee() {...} @Inject public Employee(@PathParam("id") String id) {...} }
@Path("{lastname}") @RequestScoped public class EmpDetails {...}
31.5 Conditional HTTP Requests
JAX-RSは条件付き(conditional) GET
, PUT
HTTPリクエストをサポートしています。条件付きGET
リクエストはクライアント処理の効率を改善することで帯域を節約します。
GET
リクエストは、表現が以前のリクエストから変更されていない場合には、Not Modified (304)レスポンスを返すことが可能です。たとえば、webサイトは、以前のリクエストから変更されていないすべての静的イメージに対して、304レスポンスを返すことが可能です。
PUT
リクエストは、表現が最後のリクエストから変更されていない場合には、Precondition Failed (412)レスポンスを返すことが可能です。条件付きPUT
によって、ロストアップデート問題(lost update problem)*2を避けることができます。
条件付きHTTPリクエストではLast-Modified
とETag
ヘッダーを使用可能です。Last-Modified
ヘッダーは秒単位の粒度で日時を表現可能です。
@Path("/employee/{joiningdate}") public class Employee { Date joiningdate; @GET @Produces("application/xml") public Employee(@PathParam("joiningdate") Date joiningdate, @Context Request req, @Context UriInfo ui) { this.joiningdate = joiningdate; ... this.tag = computeEntityTag(ui.getRequestUri()); if (req.getMethod().equals("GET")) { Response.ResponseBuilder rb = req.evaluatePreconditions(tag); if (rb != null) { throw new WebApplicationException(rb.build()); } } } }
このコードでは、Employee
のコンストラクタはリクエストURIからentity tagを算出し、そのタグを引数にrequest.evaluatePreconditions
メソッドを呼び出します。もしクライアントリクエストが、算出したentity tagと同じ値を持つIf-none-match
ヘッダーを返す場合、evaluate.Preconditions
は304ステータスコードとentity tagが設定されたpre-filled-outレスポンスを返します。
31.6 Runtime Content Negotiation
@Produces
と@Consumes
はJAX-RSで静的なコンテンツネゴシエーションを処理します。このアノテーションにはサーバーのコンテンツ設定を指定します。Accept
, Content-Type
, Accept-Language
などのHTTPヘッダーでクライアントのコンテンツネゴシエーション設定を定義します。
コンテンツネゴシエーション用のHTTPヘッダーに関する詳細はHTTP /1.1 - Content Negotiationを参照してください(http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html)。
以下のコードはサーバーのコンテンツ設定の例です。
@Produces("text/plain") @Path("/employee") public class Employee { @GET public String getEmployeeAddressText(String address) {...} @Produces("text/xml") @GET public String getEmployeeAddressXml(Address address) {...} }
以下のようなHTTPリクエストでgetEmployeeAddressText
が呼び出されます。
GET /employee Accept: text/plain
これは以下のようなレスポンスを生成します。
500 Oracle Parkway, Redwood Shores, CA
以下のようなHTTPリクエストでgetEmployeeAddressXml
が呼び出されます。
GET /employee Accept: text/xml
これは以下のようなレスポンスを生成します。
<address street="500 Oracle Parkway, Redwood Shores, CA" country="USA"/>
静的なコンテンツネゴシエーションでは、クライアントとサーバに複数のコンテンツとメディアタイプを定義可能です。
@Produces("text/plain", "text/xml")
静的なコンテンツネゴシエーションに加えて、JAX-RSはjavax.ws.rs.core.Variant
とRequest
を使用したランタイムコンテンツネゴシエーションをサポートしています。Variant
はコンテンツネゴシエーションのリソース表現を指定します。Variant
の各インスタンスは、メディアタイプ・言語・エンコーディングを持つことが出来ます。Variant
オブジェクトはサーバがサポートするリソース表現を定義します。表現のvariantのリストを構築するにはVariant.VariantListBuilder
を使用します。
以下のコードはリソース表現variantリストを生成する方法を示しています。
List<Variant> vs = Variant.mediatypes("application/xml", "application/json") .languages("en", "fr").build();
このコードはVariantListBuilder
のbuild
メソッドを呼んでいます。mediatypes
, languages
, encodings
メソッドを呼ぶ場合にVariantListBuilder
が呼び出されます。リソース表現の組み合わせをビルドするにはbuild
メソッドを使用します。build
メソッドで作成したVariant
のリストが、mediatypes
, languages
, encodings
で指定した項目の組み合わせとなります。
この例では、vs
オブジェクトのサイズは4になり、その内訳は以下となります。
[["application/xml","en"], ["application/json","en"], ["application/xml","fr"],["application/json","fr"]]
javax.ws.rs.core.Request.selectVariant
メソッドがVariant
オブジェクトのリストを受け入れ、HTTPリクエストにマッチするVariant
オブジェクトを選択します。このメソッドはVariant
オブジェクトのリストと、HTTPリクエストヘッダーのAccept
, Accept-Encoding
, Accept-Language
, Accept-Charset
とを比較します。
以下のコードはselectVariant
メソッドを使用して、クライアントリクエストの値から受入可能なVariant
を選択する方法を示しています。
@GET public Response get(@Context Request r) { List<Variant> vs = ...; Variant v = r.selectVariant(vs); if (v == null) { return Response.notAcceptable(vs).build(); } else { Object rep = selectRepresentation(v); return Response.ok(rep, v); } }
selectVariant
はリクエストにマッチするVariant
オブジェクトを返し、もしマッチするものがなければnullになります。このコードでは、メソッドがnullを返す場合は、受入不能レスポンス(nonacceptable response)用のResponse
オブジェクトを構築します。そうでない場合は、Variant
とObject
エンティティ形式の表現をOKステータスで保持するResponse
を返します。
31.7 Using JAX-RS with JAXB
Java Architecture for XML Binding (JAXB)はXML-to-Javaのバインディング技術で、スキーマとJavaオブジェクトの変換と、XMLインスタンスドキュメントとJavaオブジェクトインスタンス間の変換を可能にすることで、webサービスの開発を簡易化します。XMLスキーマはデータ要素とXMLドキュメントの構造を定義します。JavaクラスとXMLスキーマ間のマッピングを確立するのにツールとJAXB APIを使用可能です。JAXBはXMLドキュメントとJavaオブジェクトの相互変換を可能にするツールを提供します。
JAXBを使用することで、以下のような方法でデータオブジェクトが操作可能になります。
- XMLスキーマ定義(XSD)から始めるには
xjc
を使用します。これはJAXBスキーマコンパイラーツールで、要素とXSDスキーマ定義型とのマッピングを行うためのJAXBアノテーションを付与されたJavaクラス群を生成します。 - Javaクラス群から始めるには
schemagen
を使用します。これはJAXBスキーマジェネレーターツールで、XMLスキーマを生成します。 - XMLスキーマとJavaクラス間のマッピングを終えたら、JAXBバインディングランタイムを使用してXMLドキュメントとJavaオブジェクトを相互にマーシャル/アンマーシャルが可能になり、生成されたJavaクラスをwebサービスアプリケーションに組み込みます。
XMLはRESTfulサービスが生成および受入するメディアフォーマットとして一般的に使用されています。XMLをデシリアライズ/シリアライズには、JAXBアノテーションが付与されたオブジェクトでリクエストやレスポンスを表現可能です。JAX-RSアプリケーションではXMLデータの操作にJAXBオブジェクトを使用可能です。JAXBオブジェクトはリクエストエンティティパラメータとレスポンスエンティティで使用可能です。JAXBオブジェクトをエンティティとして読み込みと書き込みを行うための標準プロバイダインタフェースMessageBodyReader
とMessageBodyWriter
が、JAX-RSランタイム環境には含まれています。
JAX-RSでは、リソースをパブリッシュすることでサービスがアクセス可能になります。リソースはただ単にJAX-RSアノテーションをいくつか付与しただけのJavaクラスです。そのアノテーションは以下を表現しています。
アプリケーションにおけるリソースを定義するには、公開したいデータのタイプを考える必要があります。個々のユーザ環境には、ユーザに公開したい情報が格納されている関係データベースが既にあるかもしれませんし、データベース内には存在しないがリソースとしては配布する必要のある静的コンテンツを持っているかもしれません。JAX-RSでは、複数ソースからのコンテンツ配布が可能です。RESTful webサービスはリクエストとレスポンスに様々なタイプの入力と出力フォーマットを使用可能です。The customer Example Applicationで解説されているcustomer
サンプルはXMLを使用しています。
リソースは表現を持ちます。リソースの表現とはHTTPメッセージのコンテンツであり、URIを使用したリソースに送信または受信されます。それぞれのリソース表現はその表現に対応するメディアタイプを持ちます。たとえば、もしリソースがXMLフォーマットのコンテンツを返す場合、HTTPメッセージの関連メディアタイプとしてapplication/xml
を使用可能です。アプリケーションの要求に応じて、単一のフォーマットか複数のフォーマットか、リソースは望みの条件で表現を返すことが可能です。読み込みと書き込みを行うリソースメソッドが受け入れ可能なメディアタイプを宣言するのに、JAX-RSでは@Consumes
と@Produces
を提供しています。
また、JAX-RSはエンティティプロバイダを使用してJavaの型とリソース表現との相互マッピングを行います。MessageBodyReader
エンティテイプロバイダはリクエストエンティテイを読み込んでJavaの型にデシリアライズします。MessageBodyWriter
エンティテイプロバイダはJavaの型からレスポンスエンティティにシリアライズします。たとえば、String
をリクエストエンティティパラメータとして使用する場合、MessageBodyReader
エンティテイプロバイダはリクエストボディにString
をデシリアライズします。もしJAXBの型をリソースメソッドの戻り値型として使用する場合、MessageBodyWriter
はJAXBオブジェクトをレスポンスボディにシリアライズします。
デフォルトでは、JAX-RSランタイム環境はJAXBクラス用のデフォルトのJAXBContext
クラスを生成して使用を試行します。しかし、もしデフォルトのJAXBContext
クラスが適さない場合、JAX-RSのContextResolver
プロバイダーインタフェースを使用してアプリケーションにJAXBContext
クラスを提供出来ます。
以降のセクションではJAX-RSリソースメソッドでのJAXBの使用方法を解説します。
31.7.1 Using Java Objects to Model Your Data
もし公開したいデータ用のXMLスキーマ定義が無い場合、データをJavaクラスとしてモデル化し、JAXBアノテーションを付与して、XMLスキーマを生成できます。たとえば、公開したいデータが製品とそのID・氏名・説明・価格のコレクションな場合、以下のようなJavaクラスとしてモデル化が可能です。
@XmlRootElement(name="product") @XmlAccessorType(XmlAccessType.FIELD) public class Product { @XmlElement(required=true) protected int id; @XmlElement(required=true) protected String name; @XmlElement(required=true) protected String description; @XmlElement(required=true) protected int price; public Product() {} // Getter and setter methods // ... }
対応するXMLスキーマ定義を生成するためにコマンドラインでJAXBスキーマジェネレーターを実行します。
schemagen Product.java
このコマンドは.xsd
ファイルとしてXMLスキーマを生成します。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="product" type="product"/> <xs:complexType name="product"> <xs:sequence> <xs:element name="id" type="xs:int"/> <xs:element name="name" type="xs:string"/> <xs:element name="description" type="xs:string"/> <xs:element name="price" type="xs:int"/> </xs:sequence> <xs:complexType> </xs:schema>
マッピング完了後は、アプリケーションでProduct
を生成し、戻り値にしたり、JAX-RSリソースメソッドの引数として使用が可能になります。JAX-RSランタイムはJAXBを使用して、リクエストのXMLデータをProduct
オブジェクトに変換したり、Product
オブジェクトをレスポンス用のXMLデータに変換したりします。以下のリソースクラスが簡単な例になります。
@Path("/product") public class ProductService { @GET @Path("/get") @Produces("application/xml") public Product getProduct() { Product prod = new Product(); prod.setId(1); prod.setName("Mattress"); prod.setDescription("Queen size mattress"); prod.setPrice(500); return prod; } @POST @Path("/create") @Consumes("application/xml") public Response createProduct(Product prod) { // Process or store the product and return a response // ... } }
NetBeans IDEなどの一部のIDEでは、プロジェクトにJAXBアノテーション付与したクラスを追加すると、ビルド処理中に自動的にスキーマジェネレータツールを実行するものもあります。詳細な例については、The customer Example Applicationを参照してください。customer
サンプルにはデータをモデル化するJavaクラス間に複雑な関連を持つものが含まれており、これは深い階層を持つXML表現を生成します。
31.7.2 Starting from an Existing XML Schema Definition
公開したいデータ用に.xsd
ファイルでXMLスキーマ定義を作成済みの場合、JAXBスキーマコンパイラーツールを使用できます。.xsd
ファイルの簡単な例を考えてみます。
<xs:schema targetNamespace="http://xml.product" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:myco="http://xml.product"> <xs:element name="product" type="myco:Product"/> <xs:complexType name="Product"> <xs:sequence> <xs:element name="id" type="xs:int"/> <xs:element name="name" type="xs:string"/> <xs:element name="description" type="xs:string"/> <xs:element name="price" type="xs:int"/> </xs:sequence> </xs:complexType> </xs:schema>
コマンドラインで以下のようにしてスキーマコンパイラーツールを実行します。
xjc Product.xsd
このコマンドは.xsd
ファイルで定義されている型に対応するJavaクラスのソースコードを生成します。スキーマコンパイラーツールは.xsd
ファイルで定義されているcomplexType
ごとにJavaクラスを生成します。生成されるJavaクラスの各フィールドはcomplexType
内の要素に相当し、クラスにはそのフィールド用のgetter/setterメソッドが含まれます。
上記サンプルの場合、スキーマコンパイラーツールはproduct.xml.Product
とproduct.xml.ObjectFactory
クラスを生成します。Product
クラスにはJAXBアノテーションと.xsd
定義に対応するフィールドが含まれます。
@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "Product", propOrder = { "id", "name", "description", "price" }) public class Product { protected int id; @XmlElement(required = true) protected String name; @XmlElement(required = true) protected String description; protected int price; // Setter and getter methods // ... }
アプリケーションでProduct
クラスのインスタンスを(たとえばDBの値を用いて)生成できます。オブジェクトをJAXB要素に変換するためのメソッドが生成されたproduct.xml.ObjectFactory
に含まれます。このメソッドはJAX-RSリソースメソッド内でXMLを返すために使用可能です。
@XmlElementDecl(namespace = "http://xml.product", name = "product") public JAXBElement<Product> createProduct(Product value) { return new JAXBElement<Product>(_Product_QNAME, Product.class, null, value); }
以下のコードはJAX-RSメソッドでXMLとしてJAXB要素を返すのに生成クラスを使用する例を示しています。
@Path("/product") public class ProductService { @GET @Path("/get") @Produces("application/xml") public JAXBElement<Product> getProduct() { Product prod = new Product(); prod.setId(1); prod.setName("Mattress"); prod.setDescription("Queen size mattress"); prod.setPrice(500); return new ObjectFactory().createProduct(prod); } }
@POST
と@PUT
のリソースメソッドでは、引数として直接Product
を使用可能です。JAX-RSはリクエストのXMLデータをProduct
オブジェクトにマッピングします。
@Path("/product") public class ProductService { @GET // ... @POST @Path("/create") @Consumes("application/xml") public Response createProduct(Product prod) { // Process or store the product and return a response // ... } }
31.7.3 Using JSON with JAX-RS and JAXB
JAX-RSはJAXBを使用してXMLの読み込みと書き込みを自動的に出来ますが、JSONデータも同様に処理出来ます。JSONはJavaScriptが生成するシンプルなテキストベースのデータ交換フォーマットです。前述の例で言うと、製品のXML表現は以下のようになりますが、
<?xml version="1.0" encoding="UTF-8"?> <product> <id>1</id> <name>Mattress</name> <description>Queen size mattress</description> <price>500</price> </product>
同等なJSON表現は以下のようになります。
{ "id":"1", "name":"Mattress", "description":"Queen size mattress", "price":500 }
JSONデータのレスポンスを生成するためにリソースメソッドでは@Produces
にapplication/json
やMediaType.APPLICATION_JSON
フォーマットを追加可能です。
@GET @Path("/get") @Produces({"application/xml","application/json"}) public Product getProduct() { ... }
この例では、デフォルトのレスポンスはXMLですが、クライアントがGET
リクエストのヘッダーに以下を含める場合にはJSONオブジェクトがレスポンスとなります。
Accept: application/json
リソースメソッドはJAXBアノテーションの付与クラスされたクラスでJSONデータを受けることも可能です。
@POST @Path("/create") @Consumes({"application/xml","application/json"}) public Response createProduct(Product prod) { ... }
POST
リクエストでJSONデータをサブミットする場合、クライアントには以下のヘッダーを含めます。
Content-Type: application/json
31.8 The customer Example Application
このセクションではcustomer
サンプルアプリケーションのビルドと実行方法について解説します。このアプリケーションはRESTful webサービスで、JAXBを使用して特定エンティティの生成・読み込み・更新・削除(CRUD)を行います。
customer
サンプルアプリケーションはtut-install/examples/jaxrs/customer/
*3ディレクトリにあります。サンプルアプリケーションの基本的なビルドと実行についてはChapter 2, "Using the Tutorial Examples,"を参照してください。
31.8.1 Overview of the customer Example Application
アプリケーションのソースファイルはtut-install/examples/jaxrs/customer/src/main/java/
にあります。また、アプリケーションは三つの部分に分けられます。
Customer
とAddress
のエンティティクラス。このクラスはアプリケーションのモデルデータでJAXBアノテーションを含みます。CustomerService
リソースクラス。このクラスにはJAX-RSのリソースメソッドが含まれます。このメソッドはCustomer
インスタンス上の操作を実行し、このインスタンスはJAXBを使用してJSONデータやXMLとして表現されるものです。CustomerBean
セッションビーンwebクライアント用のバッキングビーンとして振る舞います。CustomerBean
はCustomerService
のメソッドを呼ぶのにJAX-RSクライアントAPIを使用します。
customer
サンプルアプリケーションはJAXBアノテーションを用いてJavaクラスとしてデータエンティティをモデル化する方法を示しています。
31.8.2 The Customer and Address Entity Classes
以下のクラスは顧客の住所を表現しています。
@Entity @Table(name="CUSTOMER_ADDRESS") @XmlRootElement(name="address") @XmlAccessorType(XmlAccessType.FIELD) public class Address { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @XmlElement(required=true) protected int number; @XmlElement(required=true) protected String street; @XmlElement(required=true) protected String city; @XmlElement(required=true) protected String province; @XmlElement(required=true) protected String zip; @XmlElement(required=true) protected String country; public Address() { } // Getter and setter methods // ... }
@XmlRootElement(name="address")
はこのクラスをaddress
XML要素にマッピングします。@XmlAccessorType(XmlAccessType.FIELD)
はこのクラスのすべてフィールドがデフォルトでXMLにバインドされることを意味します。@XmlElement(required=true)
はこの要素がXML表現に存在しなければならないことを意味します。
以下のクラスは顧客を表現するものです。
@Entity @Table(name="CUSTOMER_CUSTOMER") @NamedQuery( name="findAllCustomers", query="SELECT c FROM Customer c " + "ORDER BY c.id" ) @XmlRootElement(name="customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @Id @GeneratedValue(strategy = GenerationType.AUTO) @XmlAttribute(required=true) protected int id; @XmlElement(required=true) protected String firstname; @XmlElement(required=true) protected String lastname; @XmlElement(required=true) @OneToOne protected Address address; @XmlElement(required=true) protected String email; @XmlElement (required=true) protected String phone; public Customer() {...} // Getter and setter methods // ... }
Customer
クラスは@XmlAttribute(required=true)
を除いて前述のクラスと同様なJAXBアノテーションを持っています。@XmlAttribute(required=true)
はXML要素表現クラスの属性とプロパティをマッピングします。
Customer
クラスは別のエンティテイであるAddress
クラス型のプロパティを持ちます。この仕組みはエンティティ間の階層関係を.xsd
を書かずにJavaコードで定義可能なものです。
JAXBは前述の二つのクラスで以下のXMLスキーマ定義を生成します。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="address" type="address"/> <xs:element name="customer" type="customer"/> <xs:complexType name="address"> <xs:sequence> <xs:element name="id" type="xs:long" minOccurs="0"/> <xs:element name="number" type="xs:int"/> <xs:element name="street" type="xs:string"/> <xs:element name="city" type="xs:string"/> <xs:element name="province" type="xs:string"/> <xs:element name="zip" type="xs:string"/> <xs:element name="country" type="xs:string"/> </xs:sequence> </xs:complexType> <xs:complexType name="customer"> <xs:sequence> <xs:element name="firstname" type="xs:string"/> <xs:element name="lastname" type="xs:string"/> <xs:element ref="address"/> <xs:element name="email" type="xs:string"/> <xs:element name="phone" type="xs:string"/> </xs:sequence> <xs:attribute name="id" type="xs:int" use="required"/> </xs:complexType> </xs:schema>
31.8.3 The CustomerService Class
CustomerService
クラスのcreateCustomer
メソッドはCustomer
ベースの顧客リソースを生成して新しいリソースのURIを返します。
@Stateless @Path("/Customer") public class CustomerService { public static final Logger logger = Logger.getLogger(CustomerService.class.getCanonicalName()); @PersistenceContext private EntityManager em; private CriteriaBuilder cb; @PostConstruct private void init() { cb = em.getCriteriaBuilder(); } ... @POST @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response createCustomer(Customer customer) { try { long customerId = persist(customer); return Response.created(URI.create("/" + customerId)).build(); } catch (Exception e) { logger.log(Level.SEVERE, "Error creating customer for customerId {0}. {1}", new Object[]{customer.getId(), e.getMessage()}); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } } ... private long persist(Customer customer) { try { Address address = customer.getAddress(); em.persist(address); em.persist(customer); } catch (Exception ex) { logger.warning("Something went wrong when persisting the customer"); } return customer.getId(); }
クライアントに返すレスポンスには新規作成されたリソースのURIが含まれています。戻り値の型はレスポンスのプロパティをマッピングしたエンティティボディです、また、レスポンスのステータスプロパティでステータスコードを指定します。WebApplicationException
は404, 406, 415, 500などの適切なHTTPエラーステータスコードをラップするためのRuntimeException
です。
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
および@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
は適切なMIMEクライアントを使用するためにリクエストレスポンスのメディタタイプを設定しています。このアノテーションはリソースメソッド・リソースクラスあるいはエンティテイプロバイダにに適用可能です。もしこれらのアノテーションを使用しない場合、JAX-RSは任意のメディアタイプ("*/*"
)の使用を許可します。
以下のコードはgetCustomer
とfindbyId
メソッドの実装を示しています。getCustomer
メソッドは@Produces
を使用してCustomer
オブジェクトを返し、このオブジェクトはクライアントが指定するAccept:
に応じてXMLもしくはJSON表現に変換されます。
@GET @Path("{id}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Customer getCustomer(@PathParam("id") String customerId) { Customer customer = null; try { customer = findById(customerId); } catch (Exception ex) { logger.log(Level.SEVERE, "Error calling findCustomer() for customerId {0}. {1}", new Object[]{customerId, ex.getMessage()}); } return customer; } ... private Customer findById(String customerId) { Customer customer = null; try { customer = em.find(Customer.class, customerId); return customer; } catch (Exception ex) { logger.log(Level.WARNING, "Couldn't find customer with ID of {0}", customerId); } return customer; }
31.8.4 Using the JAX-RS Client in the CustomerBean Classes
customer
サンプルアプリケーションではクライアントの記述にはJAX-RS Client APIを使用しています。
CustomerBean
EJBクラスがCustomerService
webサービスのテストにJAX-RS Client APIを呼び出しています。
@Named @Stateless public class CustomerBean { protected Client client; private static final Logger logger = Logger.getLogger(CustomerBean.class.getName()); @PostConstruct private void init() { client = ClientBuilder.newClient(); } @PreDestroy private void clean() { client.close(); } public String createCustomer(Customer customer) { if (customer == null) { logger.log(Level.WARNING, "customer is null."); return "customerError"; } String navigation; Response response = client.target("http://localhost:8080/customer/webapi/Customer") .request(MediaType.APPLICATION_XML) .post(Entity.entity(customer, MediaType.APPLICATION_XML), Response.class); if (response.getStatus() == Status.CREATED.getStatusCode()) { navigation = "customerCreated"; } else { logger.log(Level.WARNING, "couldn''t create customer with " + "id {0}. Status returned was {1}", new Object[]{customer.getId(), response.getStatus()}); navigation = "customerError"; } return navigation; } public String retrieveCustomer(String id) { String navigation; Customer customer = client.target("http://localhost:8080/customer/webapi/Customer") .path(id) .request(MediaType.APPLICATION_XML) .get(Customer.class); if (customer == null) { navigation = "customerError"; } else { navigation = "customerRetrieved"; } return navigation; } public List<Customer> retrieveAllCustomers() { List<Customer> customers = client.target("http://localhost:8080/customer/webapi/Customer") .path("all") .request(MediaType.APPLICATION_XML) .get(new GenericType<List<Customer>>() {}); return customers; } }
このクライアントはPOST
とGET
メソッドを使用しています。
正常終了を示すHTTPステータスコードは、POST
では201、GET
では200、DELETE
では204です。HTTPステータスコードの詳細な意味については http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html を参照してください。
31.8.5 Running the customer Example
このセクションではNetBeans IDEかMavenのどちらかを使用して、customer
サンプルのビルド・パッケージング・デプロイ・実行を行う手順を説明します。
31.8.5.1 To Build, Package, and Deploy the customer Example Using NetBeans IDE
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- FileメニューからOpen Projectを選択
- Open Projectダイアログボックスから以下のディレクトリに移動。
tut-install/examples/jaxrs
*4 customer
フォルダを選択。- Open Projectをクリック。
- Projectsタブで
customer
プロジェクトを右クリックしてBuildを選択。
このコマンドはtarget
ディレクトリにアプリケーションをcustomer.war
ファイルにビルドしてパッケージングし、それからWARファイルをGlassFish Serverにデプロイします。 - 以下のURLでブラウザのwebクライアントを開きます。
http://localhost:8080/customer/
webクライアントで顧客の作成と閲覧が可能です。
31.8.5.2 To Build, Package, and Deploy the customer Example Using Maven
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- ターミナルで以下に移動:
tut-install/examples/jaxrs/customer/
- 以下のコマンドを実行。
mvn install
このコマンドはアプリケーションをビルドしてパッケージングしてtarget
ディレクトリにWARファイルを生成します。それから、GlassFish ServerにWARファイルをデプロイします。
http://localhost:8080/customer/
webクライアントで顧客の作成と閲覧が可能です。
関連リンク
- The Java EE 7 Tutorialのテキトー翻訳まとめ - Qiita - Java EE 7 Tutorialのうち、自分がテキトー翻訳したものの一覧
*1:このすぐ後の文で説明されるように、CDIが「外」からインスタンスを生成可能でなければならない、という意味合い
*3:https://java.net/projects/javaeetutorial/sources/svn/show/trunk/examples/jaxrs/customer
*4:https://java.net/projects/javaeetutorial/sources/svn/show/trunk/examples/jaxrs