REST (REpresentational State Transfer) は World Wide Web のような分散型ハイパーメディア・システムのためのソフトウェア・アーキテクチャーのスタイルです。リソース、つまり特定の情報のソースは、REST の重要な部分です。各リソースはグローバルな識別子 (例えば HTTP における URI など) によって参照されます。ネットワークのコンポーネント (ユーザー・エージェントやオリジン・サーバーなどと呼ばれます) はリソースを操作するために、標準化されたインターフェース (HTTP) を使用して通信を行い、リソースの表現を交換します。
Apache Wink は、RESTful な Web サービスを作成するために使用できるフレームワークです。Wink は REST のアーキテクチャー・スタイルに従ってサービスをモデル化する手段となるため、Wink を使用すると REST Web サービスを容易に作成および利用することができます。Wink には、サービスを構成するリソース、表現、そして統一的な手段を定義して実装するのに必要なインフラストラクチャーが用意されています。また、Apache OpenJPA フレームワークは、オブジェクトの永続化を実現する方法を定義するオープンソースのソフトウェアです。
この記事では、Wink、OpenJPA、そして REST サービスを使用して、リソースに対して HTTP 操作を実行する方法や、モデル化と URI の設計、さらには Wink のアノテーションを使用してサンプルのリソースに対して CRUD (Create、Retrieve、Update、Delete) 操作を行う方法について説明します。
この記事で使用するソース・コードはダウンロードすることができます。
すべてのリソースを XML や JSON といった特定の構造にモデル化する必要があります。リソースのすべての属性と値は、それぞれに対応する XML の属性名と属性値にマッピングされます。この記事では例として、「従業員」(Employee) をリソースと考えましょう。各従業員には、ID、名前、住所、e-メール、電話番号など、その従業員に一意の属性があります。リスト 1 は Employee リソースの XML の構造を示しています。
リスト 1. Employee リソースの XML の構造
<employee>
<employeeId>101</employeeId>
<employeeName>employee Name</employeeName>
<address>XYZ C Block, Bangalore-560025</address>
<email>employee email</email>
<telephone>5551234567</telephone>
</employee>
|
Apache Wink フレームワークは、XML または JSON と Java オブジェクトとの間のマーシャリングとアンマーシャリングを行うために JAXB アノテーションをサポートしています。OpenJPA は Java オブジェクトをリレーショナル・データストアに永続化するためのフレームワークとして機能します。
OpenJPA のエンティティーはデータストアのレコードを表現する永続オブジェクトです。この記事で説明する例では、_Employee は Employee
オブジェクトを表現するエンティティー・モデルです。エンティティー・クラス _Employee
の各永続インスタンスは一意のデータストア・レコード (一意に決まる従業員)
を表現します。エンティティー・モデルを定義するためにはアノテーションを使用します。Employee の場合には、@Table
(name="CF_EMPLOYEE") を使用して Employee のエンティティーを定義します。
すべてのエンティティー・クラスは 1
つまたは複数のフィールドを宣言する必要があり、それらのフィールド全体でインスタンスの永続識別子を構成します。この記事では従業員を例として説明しますが、従業員の一意の識別子として
employeeId を使用します。OpenJPA はエンティティーの version フィールドを使用することで、同じデータストア・レコードに対して同時に変更が行われているかどうかを検出します。そのために
@Version アノテーションを使用します。リスト 2 に抜粋したコードは Employee リソースのエンティティー・モデルを作成する方法を示しています。
リスト 2. Employee リソースに対する OpenJPA モデル
package openJpa.model;
/**
* Persistent Class for Employee
* OpenJPA enables writing persistent classes in a very transparent way
*/
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
/*Each entity represents a relational datastore record.
In this code all employee records will be stored in a table called CF_EMPLOYEE */
@Table(name="CF_EMPLOYEE")
/*
Databases allow Native sequences to be created , which are database
structures that generates increasing numeric values.
SequenceGenerator annotation represents a named database Sequence
*/
@SequenceGenerator(name="employeeSeqGen", sequenceName="native(Sequence=CF_EMPLOYEE_SEQ)")
public class _Employee implements Serializable {
private static final long serialVersionUID = 1L;
public _Employee() {
super();
}
/*All entity classes must declare one or more fields which together
forms the persistent identity of an instance.Every employee will have an
unique employeeID which is the persistent identity */
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="employeeSeqGen")
private int employeeId;
/* OPEN JPA uses version field in entities to detect concurrent
modifications to the same datastore record.Version field is optional
for any entity, but without one concurrent threads or processes might succeed
in making conflicting changes to the same record at the same time. */
@Version
private int version;
private String employeeName;
private String address;
private String email;
private int telephone;
public void setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
public int getEmployeeId() {
return employeeId;
}
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getEmployeeName() {
return employeeName;
}
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setTelephone(int telephone) {
this.telephone = telephone;
}
public int getTelephone() {
return telephone;
}
}
|
エンティティー・モデルを作成したら、データストアとの接続を確立するための接続ロジックを実装する必要があります
(データストアはどのようなタイプのデータベースでも構いません)。そのためには、OpenJPA
とデータベースとの接続ロジックを含む別個のレイヤーを作成します。単純な方法としては、データベースに関係するすべての構成をプロパティー・ファイルに含め、データベース接続を抽象化するクラス
(例えば OpenJpaAdminService など) を定義します。データベースに接続するためのロジックには
getEntityManager() メソッドを使用します。このメソッドが OpenJpaAdminService から呼び出された場合にプロパティー・ファイルの参照が呼び出されるようにすると、OpenJPA は接続先のデータベースを認識することができます。
ダウンロードに含まれている例では、OpenJpaMediator.java クラスが OpenJPA
に関するすべての操作を処理します。この例では、このクラスのコンストラクターの中で OpenJpaAdminService.getEntityManager() 関数を呼び出し、dbConnection.properties というプロパティー・ファイルをチェックしています。データベースへ接続するには以下の 2 通りの方法があります。
- JDBC ドライバーを使用して接続の詳細を処理する一般的な方法
- JNDI ルックアップを使用して JDBC 接続を取得する方法 (ダウンロード・ファイルの OpenJpaAdminService.java を参照)
Apache Wink は REST Web サービス (一意の URI で指定されるリソース) を単純に実装、利用するためのフレームワークです。各リソースは 1 つまたは複数の表現を持ち、Web サービスを呼び出すと、それらの表現がクライアントとサービスの間で交換されます。Wink には以下の特徴があります。
- RESTful なアーキテクチャー・スタイルでリソースを定義、実装するために必要なインフラストラクチャーを備えているため、統一的なインターフェース、複数の表現、サービスのイントロスペクションを実現することができます。
- API によって REST Web サービスをサポートしています。Wink は Java ベースの API であり、アノテーションを使用することによって Web サービスのクライアントやエンド・ポイントの作成およびデプロイメントを単純化します。
サーブレットを使用して RESTful なサービスを実装することもできますが、ビジネス・ロジックの実装に必要な HTTP コードが多くなりすぎる傾向があります。Wink は HTTP コードを完全に隠し、サーブレットを Java クラスの個々のメソッドに適切にバインドすることができます。
リソースはデータの取得や操作を行うための実用的なコンポーネントを表現します。リソース・クラスはビジネス・ロジックの実装を支援するリソース・メソッドを定義します。リスト 3 は Employee リソースに対するリソース・クラスの作成方法を示しています。
エンド・ユーザーはリソースを必ず XML または JSON
で表現します。ユーザーがリソースのインスタンスを新たに作成または更新する必要がある場合、ユーザーはそのリソースに対応する XML または JSON
を渡します。リソース・クラスの Wink アノテーションは実際に XML または JSON を Java オブジェクトにマーシャリングします。エンティティー・オブジェクト (_Employee) のインスタンスを定義するメリットとして、XML または JSON をマーシャリングすると、そのままデータストアに永続化できる状態のエンティティー・オブジェクトが得られます。つまり Wink フレームワークの機能を利用して OpenJPA のエンティティー・オブジェクトを永続化することができます。同様に、ユーザーがリソースのインスタンスを取得したい場合には、OpenJPA がデータストアからリソースを取得してエンティティー・オブジェクトを提供します。また Wink のアノテーションにより、取得したエンティティー・オブジェクトを XML または JSON にアンマーシャリングし、その XML または JSON をエンド・ユーザーに送信することもできます。
リスト 3. リソースを表現する
package rest.resource;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import openJpa.model._Employee;
/**
* @XmlAccessorType Controls whether fields or Javabean properties are
* serialized by default
* XmlAccessType.PROPERTY: Every getter/setter pair in a JAXB-bound class will
* be automatically bound to XML, unless annotated by XmlTransient. Fields are
* bound to XML only when they are explicitly annotated by some of the JAXB
* annotations.
*
*/
@XmlAccessorType(XmlAccessType.PROPERTY)
/**
* @XmlRootElement: Maps a class to an XML element
*/
@XmlRootElement(name="employee")
public class Employee {
/**
* Instantiate Enity Object of Employee to achieve integration of APACHE WINK
* and OPEN JPA
*/
private _Employee modelEmployee;
public Employee() {
super();
this.modelEmployee = new _Employee();
}
public Employee(_Employee modelEmployee) {
this.modelEmployee = modelEmployee;
}
@XmlTransient
public _Employee getEmployee() {
return this.modelEmployee;
}
public void setEmployee(_Employee modelEmployee)
{
this.modelEmployee = modelEmployee;
}
/**
@XmlElement: Maps a JavaBean property to a XML element derived from property name
*/
@XmlElement(name = "employeeName")
public String getEmployeeName() {
return modelEmployee.getEmployeeName();
}
public void setEmployeeName(String employeeName) {
modelEmployee.setEmployeeName(employeeName);
}
@XmlElement(name = "address")
public String getAddress() {
return modelEmployee.getAddress();
}
public void setAddress(String address) {
modelEmployee.setAddress(address);
}
@XmlElement(name = "email")
public String getEmail() {
return modelEmployee.getEmail();
}
public void setEmail(String email) {
modelEmployee.setEmail(email);
}
@XmlElement(name = "telephone")
public int getTelephone() {
return modelEmployee.getTelephone();
}
public void setTelephone(int telephone) {
modelEmployee.setTelephone(telephone);
}
@XmlElement(name = "employeeId")
public int getEmployeeId() {
return modelEmployee.getEmployeeId();
}
public void setEmployeeId(int employeeId) {
modelEmployee.setEmployeeId(employeeId);
}
@XmlElement(name = "version")
public int getVersion() {
return modelEmployee.getVersion();
}
public void setVersion(int version) {
modelEmployee.setVersion(version);
}
}
|
このセクションでは、Wink フレームワークと OpenJPA を使用して Employee リソースに対して実行する基本的な CRUD 操作について説明します。各操作に対して一意の URI が定義され、また各操作には 1 つの HTTP メソッドがあります。Create 操作と Update 操作の場合、リソースのデータ (Employee の詳細) を Content-Body として表現する必要があります。Employee リソースの Content-Body は JSON または XML です。Apache Wink は XML、JSON、HTML、Atom をサポートしています。表 1 は CRUD 操作の設計を示しています。
表 1. CRUD 操作
| 操作 | 説明 | リソース/URI | リソースのデータ |
|---|---|---|---|
| CREATE | ユーザーは新しいリソースを作成することができます。この記事の例での CREATE 操作には、新しい Employee の作成や既存のデータベースへの新しい Employee の追加などがあります。 HTTP メソッド: POST | /employee | Content-Body には Employee の詳細が特定の表現で含まれます。RESTful なインターフェースでは必ず、そのインターフェースがクライアントに対してどのような種類の表現をサポートするかを決定することができます。例えば、リスト 1 の XML 構造は新しい Employee を作成するための Content-Body になる場合があります。 |
| RETRIEVE | ユーザーは Employee ID を使用して既存の従業員の詳細情報を取得することができます。取得されたデータは、クライアントに対して REST
でサポートされる多様な形式で表現されます。例えば /employee/121: と指定すると ID が121 の従業員の詳細情報が得られます。HTTP メソッド: GET | /employee/{id} | GET 呼び出しには Content-Body はありません。GET は、安全で読み取り専用の冪等な呼び出しでなければならず、決してリソースの状態を変更してはなりません。 |
| UPDATE | ユーザーは既存のリソースを更新することができます。この記事の例での UPDATE 操作には、データベース内にある既存の Employee の更新などがあります。 HTTP メソッド: PUT | /employee | Content-Body には Employee の詳細が特定の表現で含まれます。RESTful なインターフェースでは必ず、そのインターフェースがクライアントに対してどのような種類の表現をサポートするかを決定することができます。例えば、リスト 1 の XML 構造は Employee を更新するための Content-Body になる場合があります。 |
| DELETE | ユーザーは Employee ID を使用して既存の従業員の詳細情報を削除することができます。削除後、そのユーザーはその従業員が適切に削除されたかどうかを知らせる HTTP レスポンスを受信します。例えば /employee/121: と指定すると ID が 121 の従業員の詳細情報が削除されます。HTTP メソッド: DELETE | /employee/{id} | DELETE 呼び出しには Content-Body がありません。 |
アクション・クラスは、HTTP を使用してリソースに対して実行可能なすべての CRUD 操作の実装を定義します。Apache Wink は HTTP リクエストを受信し、アクション・クラスに記述された該当のメソッドに対し、ラップされた HTTP リクエストをディスパッチします。HTTP リクエスト・パラメーター、リソース・メソッドの定義、MIME タイプを基に、各 HTTP リクエストをアクション・クラスのメソッドに対応させる必要があります。
リスト 4 は Employee リソースに対するアクション・クラスの作成方法を示しています。この例では Apache Wink による RESTful サービスを実装する
EmployeeAction クラスを作成しています。このクラスは 4 つのメソッドで構成され、各メソッドが 1 つの CRUD 操作に対応しています。
リスト 4. EmployeeAction クラスを実装する
package rest.action;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import openJpa.mediator.EmployeeOpenJpaMediator;
import openJpa.model._Employee;
import rest.resource.Employee;
@Path(value = "/employee")
public class EmployeeAction {
/**
* @param employee
* CREATE: @return This method is used to create new Employee
*/
@POST
@Consumes(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response persistEmployee(Employee employee) {
EmployeeOpenJpaMediator dao = new EmployeeOpenJpaMediator();
_Employee _employee = employee.getEmployee();
try {
dao.beginTransaction();
dao.persist(_employee);
dao.commitTransaction();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
dao.rollbackTransaction();
return Response.status(400).entity(
"Employee create failed!").build();
} finally {
dao.close();
}
Employee employeeCreated = new Employee(_employee);
return Response.ok(employeeCreated).build();
}
/**
* @param id
* RETRIEVE: @return This method is used to retrieve a particular Employee
through employeeId
*/
@GET
@Path("/{id}")
@Produces(value = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response findEmployee(@PathParam(value = "id") int id) {
EmployeeOpenJpaMediator employeeDao = new EmployeeOpenJpaMediator();
_Employee u = new _Employee();
try {
if ((u = (_Employee) employeeDao.getById(_Employee.class, id)) != null) {
return Response.ok(new Employee(u)).build();
} else {
return Response.status(404).entity(
"Employee id does not exist").build();
}
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return Response.status(404).entity(
"Employee id does not exist").build();
}
}
/**
* @param employee
* UPDATE: @return this method is used to update a particular Employee
already existing
*/
@PUT
@Produces(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Consumes(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response updateEmployee(Employee employee) {
EmployeeOpenJpaMediator ud = new EmployeeOpenJpaMediator();
_Employee _employee = employee.getEmployee();
try {
ud.beginTransaction();
ud.update(_employee);
ud.commitTransaction();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
ud.rollbackTransaction();
return Response.status(409).entity(
"Employee update failed!").build();
} finally {
ud.close();
}
Employee employeeUpdated = new Employee(_employee);
return Response.ok(employeeUpdated).build();
}
/**
* @param id
* DELETE: This method is used to delete a particular Employee through employeeId
*/
@DELETE
@Path("/{id}")
public Response deleteEmployee(@PathParam(value = "id") int id) {
EmployeeOpenJpaMediator pd = new EmployeeOpenJpaMediator();
_Employee _employee = (_Employee) pd.getById(_Employee.class, id);
if(_employee!=null){
try {
pd.beginTransaction();
pd.delete(_employee);
pd.commitTransaction();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
pd.rollbackTransaction();
return Response.status(400).entity(
"Employee delete had an error!").build();
} finally {
pd.close();
}
return Response.ok().build();
}
else{
return Response.status(400).entity(
"Employee ID does not exist!").build();
}
}
}
|
REST サーブレットが初期化されると、実際に Application クラスを継承する RestApp クラスが呼び出されます。RestApp により、Apache
Wink は該当の action クラスを取得して、action
クラスに記述された該当の HTTP 操作を実行することができます。つまり action クラスが Wink
フレームワークに登録されます。リスト 5 は EmployeeAction クラスに対して action クラスを呼び出すためのコードを示しています
リスト 5. EmployeeAction クラスを実装する (続き)
package rest.app;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import rest.action.EmployeeAction;
public class RestApp extends Application {
@Override
public Set<Class<?>>: getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
addResources(classes);
return classes;
}
public Set<Class<?>> addResources(Set<Class<?>>classes)
{
classes.add(EmployeeAction.class);
return classes;
}
}
|
この記事では、REST のアーキテクチャー、OpenJPA の実装、Apache Wink 標準により、RESTful なサービスの実装を単純化する方法を説明しました。OpenJPA と新しい Apache Wink フレームワークとを統合すると、REST サービスを利用してリソースに対して HTTP 操作を実行することができます。またこの記事では、OpenJPA のエンティティーのモデル化、リソースのモデル化、URI の設計、Wink のアノテーションを使用してサンプル・リソースに対して CRUD 操作を実行する方法についても説明しました。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Code for REST resources modeled using OpenJPA | OpenJPA.zip | 9KB | HTTP |
学ぶために
- Apache Wink: Apache Wink プロジェクトのさまざまな側面について学んでください。
- Apache
Wink Developer Guide: Wink フレームワークと、それを構成するビルディング・ブロックの基本を理解してください。
- 「Apache Wink による
RESTful な Web サービス: 第 2 回 Apache Wink REST 開発での高度なトピック」(developerWorks、2010年3月): Apache Wink 1.0 による開発の高度なトピックについての記事を読んでください。
- REST: ウィキペディアで REST の項目を読み、REST の概念を理解してください。
- developerWorks の Web development
ゾーン: Web ベースのさまざまなソリューションを解説した記事が豊富に用意されています。Web development 技術文書一覧に用意された、さまざまな技術記事やヒント、チュートリアル、技術標準、IBM Redbooks をご覧ください。
- developerWorks podcasts: ソフトウェア開発者のための興味深いインタビューや議論を聞いてください。
- developerWorks の
Technical events and webcasts: 最新情報を入手してください。
- developerWorks on Twitter: 今すぐ Twitter に参加して developerWorks のツイートをフォローしてください。
- developerWorks on-demand demos: 初心者のための製品インストール方法やセットアップのデモから、上級開発者のための高度な機能に至るまで、多様な話題が解説されています。
製品や技術を入手するために
- Apache Wink: Wink フレームワークの最新バージョンのソース・コードとバイナリー、およびその他のサンプル・プロジェクトを入手してください。
- IBM 製品の評価版: IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox
のオンライン試用版で、DB2、Lotus、Rational、Tivoli、および WebSphere が提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。
議論するために
- 今すぐ developerWorks
プロフィールを作成し、ウォッチ・リストを設定してください。developerWorks
コミュニティーとずっとつながっていられます。
- Web
開発に関心を持つ他の developerWorks メンバーを見つけてください。

Sathish Maiya はインドにある IBM Software Labs のスタッフ・ソフトウェア・エンジニアであり、WebSphere Partner Gateway Group に所属しています。彼はインドの Mysore University でコンピューター科学の学位を取得した他、インドの Mangalore University で生体医療工学の修士号を取得しています。彼はソフトウェアの設計、開発、テストを経験してきました。

Anish Chaube はインドにある IBM Software Labs の WebSphere Partner Gateway Group に所属するアソシエート・ソフトウェア・エンジニアです。彼は Bangalore の International Institute of Information Technology で情報技術の修士号を取得しています。彼はソフトウェアの開発とテストの両方を経験してきました。

