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")はこのクラスをaddressXML要素にマッピングします。@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を使用しています。
CustomerBeanEJBクラスがCustomerServicewebサービスのテストに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