目次


Apache Wink と OpenJPA を使用して REST リソースを作成する

Comments

はじめに

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 のエンティティーはデータストアのレコードを表現する永続オブジェクトです。この記事で説明する例では、_EmployeeEmployee オブジェクトを表現するエンティティー・モデルです。エンティティー・クラス _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 とデータストアとを接続する

エンティティー・モデルを作成したら、データストアとの接続を確立するための接続ロジックを実装する必要があります (データストアはどのようなタイプのデータベースでも構いません)。そのためには、OpenJPA とデータベースとの接続ロジックを含む別個のレイヤーを作成します。単純な方法としては、データベースに関係するすべての構成をプロパティー・ファイルに含め、データベース接続を抽象化するクラス (例えば OpenJpaAdminService など) を定義します。データベースに接続するためのロジックには getEntityManager() メソッドを使用します。このメソッドが OpenJpaAdminService から呼び出された場合にプロパティー・ファイルの参照が呼び出されるようにすると、OpenJPA は接続先のデータベースを認識することができます。

ダウンロードに含まれている例では、OpenJpaMediator.java クラスが OpenJPA に関するすべての操作を処理します。この例では、このクラスのコンストラクターの中で OpenJpaAdminService.getEntityManager() 関数を呼び出し、dbConnection.properties というプロパティー・ファイルをチェックしています。データベースへ接続するには以下の 2 通りの方法があります。

  • JDBC ドライバーを使用して接続の詳細を処理する一般的な方法
  • JNDI ルックアップを使用して JDBC 接続を取得する方法 (ダウンロード・ファイルの OpenJpaAdminService.java を参照)

REST リソースをモデル化する

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);
	}
}

CRUD 操作

このセクションでは、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
/employeeContent-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
/employeeContent-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 操作を実行する方法についても説明しました。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=784163
ArticleTitle=Apache Wink と OpenJPA を使用して REST リソースを作成する
publish-date=01132012