リソースフレームワーク 仕様
1.概要
1.1 フレームワークの目的と構成
※リソースフレームワーク チュートリアルの「0.はじめに」も併せてご参照ください。
このフレームワークは、「リソース」の形で表された情報を、HTTPのURL上にマップし
それらに対して操作を行うことでアプリケーションの動作を実現する、いわゆるREST APIを
簡単に実装するためのJava用フレームワークです。
このフレームワークはhifiveで作られるようなHTML5+JavaScriptのアプリケーションの
バックエンドとなるサーバを構築する際に利用することを想定しています。
このフレームワークは以下のような仕組みを提供します。
- リソースを表すクラスを実装する際の支援機能
- 実装されたリソースと操作を、サーバのURL空間上とHTTPメソッドにマップして
HTTPの呼び出しに応じて適切なリソースクラスを呼び出す機能 - HTTPのパラメータ、URL、ヘッダ、ボディに含まれる情報を一括して、
かつプロトコルを意識しないで扱えるようにするクラス
さらに、リソースを簡単に実装するために、
- RDBMSに情報を保存することができるリソース(の抽象クラス)(AbstractCrudResource)
- URLをファイルシステムに見立てて、ファイルを保管することができるリソース(GenericUrlTreeFileResource)
も併せて提供しています。
1.1 動作要件
このフレームワークを動作させるための要件は以下の通りです。
- Java 1.7以降
- J2EE 6以降準拠のWebコンテナ
- Tomcat 7以降を推奨。
1.2 アーキテクチャの概要
このフレームワークは次のような構成になっています。
1.3 用語
メッセージ、メッセージコンテナ
メッセージは、HTTPリクエストやHTTPレスポンスの、このフレームワークにおける内部形式であり、
HTTPリクエストやレスポンスに含まれる情報を、key-value形式で保持します。
メッセージコンテナは、多重化リクエスト(後述)において、複数のメッセージをまとめて保持するためのコンテナです。
また、メッセージに共通する情報も保持します。
メッセージメタデータ
リクエスト/レスポンスメッセージ(コンテナ)に格納されたデータのうち、フレームワークが内部的に使用するもののセットです。
リクエストボディやパラメータに含まれるデータのうち、キーにプレフィックス"__"を付与したデータは、メッセージメタデータとして扱われます
アプリケーションの利用者はこれらのメタデータを参照することができますが、書き換えてはなりません。
(プレフィックス、およびそれぞれのキー名は設定で変更可能です。)
(例外: "_method":HTTP methodの変更を行うためのメタデータのキーは固定)
- HTTPヘッダにおけるメッセージメタデータ
HTTPリクエストヘッダのうち、ヘッダフィールド名にプレフィックス"X-ResourceFw-"を付与したデータはメッセージメタデータとして扱われます。
また、HTTPの標準ヘッダのうち、いくつかのものがメッセージメタデータとして扱われることがあります。
(プレフィックスは設定で変更可能です。)
リソース
このフレームワークにおける「リソース」とは、URL上にマッピングされたデータのことを指します。
例えば、
http://localhost:8080/appname/resources/person/001
http://localhost:8080/appname/resources/person/002
http://localhost:8080/appname/resources/schedule/user0001/2012-06-24/
などが挙げられます。
REST、あるいはリソース指向アーキテクチャと呼ばれるアーキテクチャパターンでは
このリソースに対する「操作」を行っていくことで、目的の機能を実装したり、必要な
データを取得したりします。
本フレームワークにおいてもこのスタイルに則った実装を行います。
リソースクラス
URL上にマッピングされたリソースの「データ型」を表します。
URL上のあるディレクトリ以下のリソース群として表されます。
アクション
アクションリソースに対する操作を表します。
アクションはリソースクラスに実装された(Javaの)メソッドとして表現されます。
このフレームワークでは、HTTPのメソッドとパラメータに対応して以下のアクションが定義されています。
さらに、各リソースクラスにこれ以外のアクションを定義することもできます。
HTTPメソッド | パスの例(personの場合) | 送るデータ | 呼び出されるアクション | 処理内容 |
---|---|---|---|---|
GET | /resources/person/1 | なし | findById | IDによるアイテム取得 |
GET | /resources/person/ | なし | list | アイテムの全件を一覧取得 |
GET | /resources/person?__query=(クエリ文字列)※2 | なし | findByQuery | クエリによるアイテム取得 |
PUT | /resources/person/1 | JSON | insertOrUpdate | update または insert(自動判別) |
DELETE | /resources/person/1 | なし | remove | アイテム削除 |
POST | /resources/person | JSON | create | アイテム新規作成(ID採番を行う) |
※1 | /resources/person/1?__action=insert※2 | JSON | insert | アイテム挿入(ID採番済) |
※1 | /resources/person/1?__action=update | JSON | update | アイテム更新 |
※1 | /resources/person/1?__action=exists | なし | exists | 存在確認 |
※1 | /resources/person?query=(クエリ文字列)&__action=list | なし | list | 一覧取得 |
※1 | /resources/person?query=(クエリ文字列)&__action=count | なし | count | 件数取得 |
※1 | /resources/person/1?__action=lock | なし | lock | アイテムのロック取得 |
※1 | /resources/person/1?__action=unlock | なし | unlock | アイテムのロック開放 |
※1原則はPOSTですが動作上は他のメソッドでも動作します。
※2「__action 」/「__query」は、_(アンダースコア)2個+「action/query」です。
@ResourceMethodアノテーションを指定することで、これ以外のアクションも作成することができ、
action=[アクション名]を指定することで、あらかじめ定義されたアクションと同様に呼び出すことができます。
リソースアイテム
リソースクラスに属する1つ1つのデータエントリです。
これら1つ1つに対してリソースメソッドを実行します。
1.4 通信仕様
1.4.1 対応可能なデータ形式
- リクエスト
- テキスト、バイナリ
- フォームデータ(multipart含む)
- JSON
- レスポンス
- テキスト、バイナリ
- JSON
1.4.2 URL
アプリケーションのコンテキストルート以降のURLは以下の構成をとります。
/サービスルート/リソース名/・・(パス)・・
- サービスルート
この配下のURLに対するリクエストをフレームワークで処理します(設定変更可能) - リソース名
どのリソースに対するリクエストかを表します。 - パス
リソース名以外のパス部分です。
例)
- personリソースのうち、ID3番を持つリソースアイテム
- /resources/person/3
- eventリソースのうち、ID:20130401-arenaを持つリソースアイテム
- /resources/event/20130401-arena
- urltreeリソース(ファイル)のうち、パス: /dir1/test.txtを持つリソースアイテム
- /resources/urltree/dir1/test.txt
1.4.3 多重化リクエスト
1回のHTTPリクエストに複数の論理的なリクエストを含め、一括で処理させることができます。
- 多重化リクエストはJSON配列の形で表され、各要素が1個のリクエストに対応します。
- リソース名・パス、URLパラメータを各JSONデータに含めることができます。
- Cookie、HTTP method、ヘッダなどに含まれる同じキーのデータは、各個別リクエストにとってのデフォルト値となります。
2 フレームワーク仕様詳細
2.1 HTTPリクエスト→リクエストメッセージへのマッピング
Cookie < method(GET, ・・) < ヘッダ < ボディ < URL(パス) < URLパラメータ
の順で採用される。多重化リクエストでも、HTTPリクエスト単位・個別リクエスト単位それぞれで同様のルールとなります。
例) ボディ(form data)に _method = GET、URLが /・・?_method=PUT と指定されたリクエストをPOSTした場合、 → 処理はPUTになります
2.2 レスポンスメッセージ→HTTPレスポンスへのマッピング
レスポンスボディ、レスポンスヘッダを表すメタデータは、HTTPレスポンスボディ、HTTPレスポンスヘッダに設定されます。
多重化リクエストの場合、レスポンスヘッダを表すメタデータのうち、レスポンスメッセージコンテナで保持しているものだけを
HTTPレスポンスヘッダに設定します。
2.3 アクションとHTTP methodのマッピング
アクションはリソースに対する操作内容です。
リクエストデータにアクションの指定がない場合、HTTP methodごとにREST風のデフォルトアクションが実行されます(設定変更可能)。
2.4 リソースメソッドとアクションのマッピング
アクションは、リソース名との組み合わせで、リソースメソッドと対応します。
実際にどのリソースメソッドが呼ばれるかは、リソース定義に従います(後述)。
- BasicResourceインターフェースでは、
HTTPメソッドに対応するデフォルトのアクションを含んでいます。
2.5 リソースの定義
- リソースクラスアノテーション
クラスに@ResourceClassアノテーションを付与すると、そのクラスはリソースクラスとして識別されます。
オプションで、"name"が設定されればそれがリソース名となりますが、
設定されない場合はクラス名から末尾の"Resource"を除き、すべて小文字にしたものがリソース名になります。
例)
public class BigEventResource implements BasicResource {
→ リソース名は「bigevent」
public class BigEventResource implements BasicResource {
→ リソース名は「smallevent」
- リソースメソッドアノテーション
リソースクラスのpublicメソッドは自動的にリソースメソッドとなり、
そのメソッド名と同名のアクションがマッピングされます。
@ResourceMethodを付与し、"action"を設定すればそのアクションがマッピングされます。
例)
public String hogehoge
{
→ http://~/resources/[リソースクラス名]/[リソースアイテムID]?action=hoge
で呼び出すことができます。
- リソース定義プロパティ
resource-def.propertiesに
リソース名 . アクション名 = リソースクラス(完全修飾名) . リソースメソッド
の形式で記述すると、アノテーションより優先してリソースとして識別されます。
ワイルドカード("*")を使用すると、全てのリソースメソッドに対して、そのメソッド名と同名のアクションがマッピングされます。
例) person.*=com.htmlhifive.resourcefw.sample.resource.person.PersonResource.*
リソースクラスアノテーション、リソースメソッドアノテーションをインターフェースや抽象クラスに付与し、
そのサブクラスをリソースとしたい場合は、ResourceManagerのBean定義でresourceInterfaceListプロパティを設定します。
プロパティには、そのインターフェース名あるいは抽象クラス名のリストを設定します。
2.6 メタデータとその役割
メタデータの有無や内容によってフレームワークの動作を切り替えることができます。
リクエストに含まれるメタデータ
※以下、Xxxxx(XXXXX)と表記しているのは、
リクエスト時にURLやボディにXxxxxxと指定した場合、
その値は、MessageMetadata.XXXXXで取得できる、ということを意味します。
- _method(METHOD)
HTTPメソッド。プレフィックスが「_(アンダースコア1つ)」固定であることに注意してください。
- __path(REQUEST_PATH)
URLに含まれるリソースパスです。
フレームワーク内で処理が進むごとにパスを「降りていく」ので、リソースメソッドで受け取るときにはリソース名の下以降のパスが設定されています。
例) /resources/(リソース名)/1/2/3 というURLであれば 1/2/3 が、リソースメソッドで受け取るREQUEST_PATHになります。
- __action(ACTION)
リソースクラスのどのアクションを呼ぶかを表します。
- __accept(ACCEPT)
リソースがContent-Typeを設定しなかった場合に使用されるMIMEタイプです。
もし、このパラメータもリクエストに含まれていない場合は、
application/jsonがデフォルトになります。
- __query(QUERY)
リソースアイテムの検索に使用するクエリです。この文字列の解釈はリソースに任されています。
なお、AbstractCrudResource、AbstractResourceQuerySpecificationという汎用抽象クラスを使用すると
「カラム名とカラム値の配列」の配列に相当する文字列を渡し、いずれかの値と一致するレコードを返すクエリ(SQLのin句に相当)が使用できます。
- __lockToken(LOCK_TOKEN)
lockしたデータの参照、更新(削除)、unlockに必要なトークン文字列。
lockアクションで返された文字列を使用します。
- __download(REQUEST_FILE_DOWNLOAD)
trueをリクエストに含めると、ファイルとしてレスポンスを返すときにダウンロードを行います(Content-Dispositionヘッダを出力)。
ダウンロードに使用するファイル名は後述のRESPONSE_DOWNLOAD_FILE_NAMEでレスポンスデータに含める必要があります。
リソースが受け取るリクエストメッセージに含まれるメタデータ
- REQUEST_PATH_ORG
リクエストされたURLのうち、コンテキストパス以下。
例)
http://hostname:8080/hifive-resource-sample/resources/(リソース名)/1/2/3
というURLであれば
/resources/(リソース名)/1/2/3
- USER_PRINCIPAL
認証されたユーザー情報を持つjava.security.Principalオブジェクトです。
- REQUEST_CONTENT_KEY
後述の「リクエストのContent-Type別のBody解釈」参照。
リソースが返すレスポンスに含めることができるメタデータ
- RESPONSE_STATUS
ステータスコードに対応します。
ResourceActionStatus列挙型の列挙値を設定します。
- RESPONSE_HEADER
レスポンスヘッダに対応します。
Map(key-value)形式で設定します。
- RESPONSE_BODY
レスポンスボディに対応します。
リソースがレスポンスメッセージ型以外のオブジェクト返した場合は、自動的にこのキーでレスポンスメッセージに設定されます。
InputStreamやbyte[]で含める場合は、必要に応じて同時にHTTP_HEADER_CONTENT_TYPEを設定することができます。
- RESPONSE_DOWNLOAD_FILE_NAME
レスポンスのContent-Dispositionヘッダにおけるfilenameに設定されるファイル名です。
2.7 リクエストのContent-Type別のBody解釈
- application/x-www-form-urlencoded
フォームデータとして解釈し、Map<String,Object>でリクエストメッセージに設定されます。
- multipart/formdata
マルチパートのフォームデータとして解釈し、FileValueHolderオブジェクトでリクエストメッセージに設定されます。
FileValueHolderからデータのContent-Type(MIMEタイプ)やストリームを取得できます。
- application/json
JSONとして解釈し、Map<String,Object>でリクエストメッセージに設定されます。
- なし
byte[]でリクエストメッセージに設定されます。
- 上記以外
InputStreamでリクエストメッセージに含まれます。
Map<String,Object>のkey-valueはリクエストメッセージからそのkeyで取得でき、リクエストデータの上書きも適用されます。
一方、byte[]、FileValueHolder、InputStreamはMessageMetadata#REQUEST_CONTENT_KEYでリクエストメッセージからkeyをまず取り出し、
そのkeyを使うことでリクエストメッセージから取得できます。
2.8 例外とそのレスポンス
以下の例外をリソースメソッドでスローすると、対応するステータスのHTTPレスポンスを返します。
- GenericResourceException
500レスポンスに対応
- AbstractResourceExceptionのサブクラス
サブクラスそれぞれのステータスコードを持つレスポンスに対応
AbstractResourceExceptionは例外用レスポンスメッセージを生成できるようになっています(FailureResponseMessageクラス)。
独自のAbstractResourceExceptionサブクラスを実装することで、このレスポンスメッセージのRESPONSE_BODYメタデータを設定できます。
これにより200以外のレスポンスでも任意のレスポンスボディを返すことができます。
FailureResponseMessageはデフォルトで発生した例外の情報(cause, detailInfo, stacktrace)を持っていますが、
HTTPレスポンスにはこれを含んでいません。
デバッグ時などは、resource-configuration.propertiesファイルのRESPONSE_WITH_ERROR_DETAILの値を
trueに設定することで、レスポンスに例外情報を含ませることができます。
2.8.1 フレームワークが提供するAbstractResourceExceptionのサブクラス
Exception名 | 対応するHTTPのエラーコード | 説明 |
---|---|---|
BadRequestException | 400 | リクエストの書式が不正な場合にthrowします。 |
ConflictException | 409 | 更新の競合が発生した場合(例えば、 あるクライアントがデータを読み込んだ後に他のクライアントによってサーバ上のデータが既に更新され、 そのあとに最初のクライアントがデータを更新使用した場合など)にthrowします。 |
ForbiddenException | 403 | クライアントまたは利用者にそのデータを読む権限がない場合にthrowします。 |
GoneException | 410 | データが既に削除されている場合にthrowします。 |
LockedException | 423 | 更新時にデータがロックされている場合にthrowします |
NotFoundException | 404 | データが見つからない場合にthrowします。 |
NotImplementedException | 501 | その操作が実装されていない場合にthrowします。 |
ServiceUnavailableException | 503 | 一時的にサービスが利用できない場合にthrowします。 |
3 汎用抽象CRUDリソースクラス : AbstractCrudResource 実装の詳細
Spring Data JPAの機能を使い、RDBMSに対するCRUD操作を行うリソースを
簡単に実装するための抽象クラスです。
3.1 アクションの仕様
- findById
getId()で得られたID値を使用し、テーブルを主キー検索します。
REQUEST_PATHメタデータが空の場合はlistアクションを実行します。
戻り値は永続化Entity型です。
永続化EntityのIDフィールドはgetIdFieldName()メソッドで返すように実装しておきます。
- findByQuery
永続化Entityの各フィールド(テーブルのカラム)に対するselect文におけるin句に相当するクエリをデフォルトで実装、さらに独自クエリをSpring DataのSpecificationで追加できます。
(getQuerySpec()メソッドでResourceQuerySpecificationsインターフェースの実装を返すように実装しておきます)。
デフォルトクエリはQUERYメタデータをJSONまたはJSON文字列で以下のように指定します。
キーはEntityのフィールド名と一致している必要があります。
戻り値は永続化Entity型のリストです。
例) {"id":["id1",・・], "name" : ["nameA",・・], ・・}
デフォルトの実装ではクエリの値に関わりなく常に全件を返します。
- findByIdForUpdate、findByQueryForUpdate
findById、findByQueryそれぞれに対応する、DB上での悲観ロック(SELECT ~ FOR UPDATE)文に
対応する操作として定義されています。
デフォルトの実装では、findById、findByQueryをそのまま呼び出しますので、ロックは実行されません。
ロックを使用する際には必ず子クラスでしかるべく実装してください。
- list, count
findByQueryで取得できるデータのID値リスト(List<String>)、あるいは数(int)を返します。
- insert, update, delete, exists, insertOrUpdate
順に、データの挿入、更新、削除、IDによる存在確認、更新(なければ作成)、になります。
戻り値はStringで、操作したデータのIDを返します。
exists、deleteを除くアクションでは、RequestMessageに永続化Entityの各フィールドと同名のkeyが含まれていれば、
そのvalueを使用をEntityに設定します。含まれていない場合はnullが設定されます。
- lock, unlock
getId()で得られたID値を使用し、アクションを実行します。
DefaultLockManagerはロックを行わないため、lockをサポートするリソースはLockManager実装をインジェクションする必要があります。
戻り値は"id",LOCK_TOKENメタデータを文字列で保持するMap(key-value)。
- create
生成されたID値でinsertを行います。
戻り値はStringで、生成されたID値を返します。
ID生成処理は、createNewId()メソッドによって行われます。
デフォルトではランダムUUIDを使用します。必要に応じて子クラスで実装してください。
- getId
REQUEST_PATHメタデータの値をStringのID値として返します。
3.2 実装するメソッド
汎用抽象CRUDリソースのサブクラスでは、以下のメソッドをオーバーライドできます。
※印があるものはかならず実装が必要で(abstractメソッドとして定義されています)、それ以外は要件に応じて
実装してください。
- getRepository()※
そのリソースのEntityを永続化するためのJpaRepositoryを返すように実装します。
- getSpecificationExecutor()
そのリソースのEntityに対するSpring DataのSpecificationを実行するためのJpaSpecificationExecutorを返すように実装します。
多くの場合、そのリソースのRepositoryインターフェースがJpaRepositoryインターフェースとJpaSpecificationExecutorを
継承するように実装することになります。
デフォルトの実装ではnullが返ります。この場合、findByQueryは常に全件を返すように動作します。
- getQuerySpec()
AbstractResourceQuerySpecificationsのサブクラスを返すように実装します。
デフォルトの実装ではnullが返ります。この場合、findByQueryは常に全件を返すように動作します。
- createNewId()
新規のID値(String)を生成するメソッドです。
EntityのIDを通し番号にしたい場合などに実装します。
デフォルトの実装ではランダムUUIDを生成して返します。
- getIdFieldName()
そのリソースのEntityのIDフィールド名を返すように実装します。
デフォルトでは、Entityのクラスの@Id(javax.persistence.Id)を返すように
実装されています。
- findByIdForUpdate, findByQueryForUpdate
初期状態では、それぞれfindById、findByQueryと同じ動作をするので
ロックをかけたい場合に実装します。
例) JpaRepositoryインターフェースを継承したリソースのRepositoryインターフェースで、 @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("・・・") を付加したクエリメソッドを定義します。
4 汎用ファイルリソース : GenericUrlTreeFileResourceの実装詳細
ディレクトリやファイルとその属性情報(UrlTreeMetaData)を保持・管理するリソース実装です。
これを使用することで、RESTスタイルのリクエストによりファイルのアップロード、ダウンロードを行うことができるようになります。
4.1 構成の概要
4.2 アクションの仕様
BasicResourceのメソッドのうち、以下のメソッドを実装しています。
- getById
IDが示すディレクトリまたはファイルのパスにあるファイルデータを取得します。
ディレクトリが指定された場合はそのディレクトリ内に含まれるディレクトリまたはファイルの一覧をJSON形式で返します。
存在しない場合は404レスポンスが返ります。
- insertOrUpdate
IDが示すディレクトリまたはファイルのパスを更新します。
存在しない場合は新規に作成します。
- insert
IDが示すディレクトリまたはファイルのパスを新規生成します。
すでに存在する場合は409レスポンスが返ります。
- update
IDが示すディレクトリまたはファイルのパスを更新します。
存在しない場合は404レスポンスが返ります。
- remove
IDが示すディレクトリまたはファイルのパスを削除します。
存在しない場合は404レスポンスが返ります。
- lock
IDが示すディレクトリまたはファイルのパスを共有ロックにより更新不可にします。
存在しない場合は404レスポンスが返ります。
- unlock
IDが示すディレクトリまたはファイルのパスのロックを開放します。
存在しない場合は404レスポンスが返ります。
- getId
他のアクションが使用するディレクトリやファイルのパスとしてREQUEST_PATHを返します。
4.2 メタデータとその役割
4.2.1 リクエストに含むメタデータ
- type(メタデータプレフィックスは付与しないことに注意)
値が"dir"である場合に、そのリクエストのREQUEST_PATHがをディレクトリを表現していると判断します。
- metadataOnly(同上)
値が"true"である場合、ファイルデータを含まずUrlTreeMetaDataオブジェクトの内容をJSONで返します。
- __filesDir(REQUEST_MULTIPART_FILES_DIR)
これがリクエストに含まれているとき、Multipartリクエストで複数のファイルを一括でinsertOrUpdateします。
このメタデータの値がディレクトリパスとなります(空文字列の場合はベースディレクトリ直下にファイルが置かれます)。
4.2.2 ファイルが持つ属性情報
各ファイルはそれぞれ実体の他にファイルの属性情報を持っており、URLのパラメータに?metadataを追加することで
その情報を取得することができます。
例)
GET http://[your host name]:8080/hifive-resource-sample/resources/urltree/hogehoge.txt?metadata
戻り値:
{ "absolutePath" : "/hogehoge.txt",
"accessedTime" : -1,
"contentType" : "text/plain",
"createdTime" : 1338497064876,
"directory" : false,
"groupId" : "developers",
"key" : "hogehoge.txt",
"lockExpiredTime" : -1,
"lockStartTime" : -1,
"ownerId" : "hifive",
"parent" : "",
"permission" : "rwxr-x--x",
"updatedTime" : 1350877604786
}
各項目の意味は次の通りです。
- absolutePath
ルート(/)からのパス - contentType
ファイルのContent-Type - createdTime
ファイルの作成時刻 - directory
ディレクトリかどうか、ディレクトリの場合"true" - key
ファイル名 - parent
親ディレクトリ名
- permission
パーミッション。ファイルへの操作権限を表す。。
「rwxrwxrwx」の形式で、rは読み込み権、wは書き込み権、xは実行権を表し、
順に、所有者、グループ、それ以外のユーザの権限を表す。権限がない場合は「-」。
- ownerId
ファイルの所有者 - groupId
ファイルが所属するグループ