Apache Wikcet 7.x ユーザガイド9章邦訳+訳注

[原文](https://ci.apache.org/projects/wicket/guide/7.x/guide/single.html)

原文著者: Authors: Andrea Del Bene, Martin Grigorov, Carsten Hufe, Christian Kroemer, Daniel Bartl, Paul Borș, Tobias Soloschenko, Igor Vaynberg , Joachim Rohde

訳注は :sos: で示します。

訳語集

<dl>
  <dt>ページ部品</dt>
  <dd>org.apache.wicket.Component 原文では単に component<dd>
</dl>

# 9. リクエスト処理の内側

Wicketは信頼性のある包括的なオブジェクト志向によるWeb開発の抽象化を提供してはいますが、時としてユーザセッションや、Webリクエスト、クエリパラメータなどなどの『素のまま』のWeb実体を取り扱わなくてはならないことがあります。例えば任意のパラメータをユーザセッションに保存したい場合などがこれにあたります。

JavaServlet仕様の低レベルAPIを取り扱う重荷を取り除き、そうしたWeb実体に容易にアクセスするためのラッパークラスをWicketは提供しています。もちろんWicketアプリケーションの下敷きとなっている（``HttpSession`` や ``HttpServletRequest`` 等々の）標準クラスにアクセスする事はいつでも可能です。この章ではそうしたラッパークラスを紹介し、ユーザブラウザからのWebリクエストをWicketがそれらを使ってどのように取り扱っているかを説明します。

## 9.1. ``Application`` クラスとリクエスト処理

アプリケーションの初期化と設定を担う一方で、``Application``クラスはWicketがリクエストを処理するための内部実体を生成する役割も担っています。こうした実体は以下のクラスのインスタンスになります：``RequestCycle``、``Request``、``Response``そして``Session``です.

次の節ではこれらのクラスそれぞれの概要を示し、リクエスト処理にどのように関わっているかを説明します。

## 9.2. ``Request`` クラスと ``Response`` クラス

``Request`` クラスと ``Response`` クラスは ``org.apache.wicket.request`` パッケージに存在し、Webアプリケーションが利用する具体的なリクエストとレスポンスを抽象化します。

どちらのクラスも抽象クラスとして宣言されていますが、アプリケーションが ``WebApplication`` を継承している場合は、 ``org.apache.wicket.protocol.http.servlet`` パッケージに存在するサブクラス ``ServletWebRequest `` クラスと ``ServletWebResponse`` が用いられます。 ``ServletWebRequest `` と ``ServletWebResponse`` はそれぞれ ``HttpServletRequest `` と ``HttpServletResponse`` を内包しています。これらの低レベルインスタンスにアクセスsする場合は ``Request`` の ``getContainerRequest()`` メソッドと ``Response`` の ``getContainerResponse()`` メソッドを呼び出します。

## 9.3. リクエスト処理の『ディレクター』- ``RequestCycle``

``org.apache.wicket.request.cycle.RequestCycle`` クラスはWebリクエストの受け入れを担う実体です。アプリケーションはリクエストを受け取る度に ``createRequestCycle(request, response)`` メソッドによって新しい ``RequestCycle`` インスタンスを作成します。

``createRequestCycle`` メソッドは final として宣言されているため、カスタマイズした ``RequestCycle`` のサブクラスを返却するようにオーバーライドすることはできません。代わりにインタフェース ``org.apache.wicket.IRequestCycleProvider`` を実装するプロバイダを作成し ``setRequestCycleProvider `` メソッドでもってアプリケーションにそれを使うよう指示する必要があります。

現在実行中の ``RequestCycle`` は静的メソッド ``RequestCycle.get()`` を呼び出すことで何時でも取得できます。正確に言うならばこのメソッドは、リクエストの受け入れ処理を行っている現行（もしくはローカル）スレッドと対応する ``RequestCycle`` を返却します。似たような現行スレッドに用いられているインスタンスを取得するための ``get()`` メソッドは ``org.apache.wicket.Application`` （こちらについては4.2.2.節で既に見ています）と ``org.apache.wicket.Session`` にも実装されています。

:warning: この get メソッドの実装は標準クラス ``java.lang.ThreadLocal`` を利用しています。``java.lang.ThreadLocal`` クラスのスレッド・ローカル変数に関する JavaDoc を参照してください。

``org.apache.wicket.Component`` クラスは内部で ``RequestCycle.get()`` を呼び出す便利メソッド ``getRequestCycle()`` を提供しています。

```java:
public final RequestCycle getRequestCycle() {
	return RequestCycle.get();
}
```

### ``RequestCycle`` とリクエスト処理

:warning: この節ではリクエスト処理に際して裏側で何が発生するかについての基本的な情報についてのみ提示しています。Wicketを利用するにあたってこの処理をカスタマイズする必要があるとはあまり考えられないため、詳細は省きます。

リクエストを処理するにあたって、``RequestCycle`` は ```` インタフェースを実装する別の実体にその処理を以上します。このインタフェースの実装はリクエストされたリソースの種類に応じて（描写対象となるページ、AJAXリクエスト、外部ページへのURLなどなど）それぞれ異なるものが存在します。

受け取ったHTTPリクエストに対して適切なハンドラを選択するために、 ``RequestCycle`` は ``org.apache.wicket.request.IRequestMapper`` インタフェースを実装したインスタンスの集合を用います。このインタフェースではマッパーが現在のリクエストに対してどの程度互換性があるか（:sos:適切か）をスコアとして返却する ``getCompatibilityScore(Request request)`` メソッドを定義しています。 ``RequestCycle `` は最も高いスコアとなったマッパーを選択し、 ``mapRequest(Request request)`` メソッドを呼び出して受け取ったHTTPリクエストに対する適切なハンドラを取得します。適切なハンドラが取得できたならば、 ``RequestCycle`` は ``respond(IRequestCycle requestCycle)`` メソッドを呼び出してリクエスト処理を開始します。

次のシークエンス図は ``RequestCycle`` がどのようにリクエストハンドラを選択するかの総括になります：

![原文ページ転載](https://ci.apache.org/projects/wicket/guide/7.x/img/request-cycle-handler.png)

開発者は ``IRequestMapper`` の実装を追加して ``WebApplication`` クラスの ``mount(IRequestMapper mapper)`` メソッドによって開発中のアプリケーションに追加することができます。10.6.節ではWicketがマウントされたページに対する組み込みマッパーを追加する際に、このメソッドをどのように使っているかを見ることになります。

### ``urlFor`` メソッドと ``mapUrlFor`` メソッドによるURL生成


``RequestCycle`` は以下の実体のURLを（文字列として）生成する役割も担っています：

- ページクラス
  -  ``urlFor(Class<C> pageClass, PageParameters parameters) `` メソッド
- ``IRequestHandler``
  - ``urlFor(IRequestHandler handler)`` メソッド
- ``ResourceReference ``
  - ``urlFor(ResourceReference reference, PageParameters params)`` メソッド
    （リソース実体については19章で紹介します）

上記のオーバーロードされた ``urlFor`` メソッドには文字列の代わりに ``org.apache.wicket.request.Url`` インスタンスを返すバージョンも存在します。そちらのバージョンは名前の頭に『map』とついています。（つまり全体で mapUrlFor となります）

### ``setResponsePage`` メソッド

``RequestCycle`` クラスは（4.4.節で見たような）ユーザを特定のページにリダイレクトする際に用いる ``setResponsePage`` メソッドの実装も含んでいます。 ``org.apache.wicket.Component`` クラスの同名メソッドは内部的には現在の ``RequestCycle`` の実装を呼び出す単なる便利メソッドになります：

```java:
public final void setResponsePage(final Page page) {
	getRequestCycle().setResponsePage(page);
}
```

### ``RequestCycle`` のフックメソッドとリスナー

``RequestCycle`` にはオーバーライドしてリクエスト処理が特定の段階に達した時に任意の処理を実行するためのフックメソッドが存在します：

- onBeginRequest()
  - ``RequestCycle`` がリクエスト処理を開始する前に呼び出されます。
- onEndRequest()
  - ``RequestCycle`` がリクエスト処理を完了した後に呼び出されます。
- onDetach()
  - ``RequestCycle`` がリクエスト処理を完了し、スレッドから除去される直前に呼び出されます。このメソッドのデフォルト実装は現行セッションの ``detach()`` メソッドを呼び出します（``Session`` クラスについては直後の9.4.節で扱います）。

``onBeforeRequest`` メソッドと ``onEndRequest`` メソッドはビジネスコード実行の前と後に、Hivernate/JPAセッションを開いたり、コード終了時に閉じたりといった何らかのカスタムアクションを実行する必要がある場合に利用できます。

リクエスト処理と呼応するより柔軟な方法として、``org.apache.wicket.request.cycle.IRequestCycleListener`` リスナーインタフェースを利用することもできます。``RequestCycle`` で既に見た３つのメソッドに加えて、このインタフェースはリクエスト処理中のさらなるフックを提供します：

- onBeginRequest(RequestCycle cycle)
  - （上述の説明参照）
- onEndRequest(RequestCycle cycle)
  - （上述の説明参照）
- onDetach(RequestCycle cycle)
  - （上述の説明参照）
- onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
  - ``IRequestHandler`` の選択が完了した時に呼び出されます。
- onRequestHandlerScheduled(RequestCycle cycle, IRequestHandler handler)
  - ``IRequestHandler`` の実行予約が完了した時に呼び出されます。
- onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler)
  -  ``IRequestHandler`` の実行が完了した時に呼び出されます。
- onException(RequestCycle cycle, Exception ex)
  - リクエスト処理中に例外が発生した時に呼び出されます。
- onExceptionRequestHandlerResolved(RequestCycle rc, IRequestHandler rh, Exception ex)
  - ``IRequestHandler`` の選択完了後に例外が発生した時に呼び出されます。
- onUrlMapped(RequestCycle cycle, IRequestHandler handler, Url url)
  - ``IRequestHandler`` と対応する URL が生成された時に呼び出されます。

こうしたリスナーを利用する場合は ``createRequestCycle`` で新しい ``RequestCycle`` を生成する度に渡されるように、アプリケーションに追加する必要があります：

```java:
@Override
public void init() {
	super.init();

	IRequestCycleListener myListener;
	//listener initialization…
	getRequestCycleListeners().add(myListener)		
}
```

``getRequestCycleListeners`` メソッドは ``org.apache.wicket.request.cycle.RequestCycleListenerCollection`` クラスのインスタンスを返却します。このクラスは IRequestCycleListener の型付コレクションの一種で（:sos:デザインパターンで言うところの） Composite パターンを実装しています。

## ``Session`` クラス

Wicket ではクライアント情報、セッション属性、セッションレベルキャッシュ（8.2.節で見たような）等々のセッションと紐づく情報は ``org.apache.wicket.Session`` クラスでもって操作します。

加えて8.1.節で学んだように、Wicketはユーザセッションを生成して状態を持つページのバージョンを保持しています。 ``RequestCycle`` がそうであったように、新しい ``Session`` のインスタンスは ``Application`` クラスが ``newSession(Request request, Response response)`` メソッドを用いて生成します。このメソッドは final 宣言されていないため ``Session`` クラスのカスタム実装を用いたい場合はオーバーライドすることができます。

アプリケーションが ``WebApplication`` のサブクラスであった場合のデフォルトでは、 ``newSession`` メソッドは ``org.apache.wicket.protocol.http.WebSession`` クラスのインスタンスを返却します。``RequestCycle`` の説明で言及したように、現行スレッドと対応するインスタンスを取得するための静的メソッド ``get()`` を ``Session`` クラスも提供しています。

### ``Session`` とリスナー

``RequestCycle`` と同様に ``org.apache.wicket.Session`` クラスもリスナーをサポートします。 ``Session`` の場合はこれらのリスナーは ``onCreated(Session session)`` メソッドのみの　``org.apache.wicket.ISessionListener`` インタフェースのコールバックを実装します。名前から推察される通り、このメソッドは新しいセッションが生成された時に呼び出されます。``Session`` のリスナーは ``RequestCycle`` のリスナーと同様に型付コレクションでもってアプリケーションに追加する必要があります：

```java:
@Override
public void init(){
	super.init();

	//listener initialization…
	ISessionListener myListener;
	//add a custom session listener
	getSessionListeners().add(myListener)

}
```

### セッション属性の操作

``Session`` クラスは標準インタフェースの ``javax.servlet.http.HttpSession`` とほぼ同様の方法でセッション属性を操作します。セッション属性は以下のメソッドで生成、読み取り、削除します：

- setAttribute(String name, Serializable value)
  - 指定された名前の属性を生成します。セッションに同名の属性が既に存在する場合は新しい値で置き換えられます。値にはシリアライズ可能なもののみ指定できます。
- getAttribute(String name)
  - 指定された名前の属性の値、もしくは存在しない場合は null を返します。
- removeAttribute(String name)
  - 指定された名前の属性を削除します。

``WebSession`` のデフォルトでは属性の格納の底としてHTTPセッションを用います。Wicketは属性名の前に自動的に接頭辞を付与します。この接頭辞は ``WebApplication`` の ``getSessionAttributePrefix()`` メソッドが返却します。

:sos: アプリケーション間のセッション属性名競合をこの接頭辞で回避しているようです。

### HTTPセッションのアクセス方法

何らかの理由で底となる ``HttpSession`` インスタンスに直接アクセスしたい場合には、現行リクエストから以下のコードで取得できます：

```java:
HttpSession session = ((ServletWebRequest)RequestCycle.get()
		.getRequest()).getContainerRequest().getSession();
```

Wicketによって付与される接頭辞無しで特定のセッション属性を設定する必要があるような場合に、素のままのセッションインスタンスが必要になります。具体例としては Tomcat を Webサーバとして利用しているとしましょう。Tomcatの提供する管理ツールの一つに、指定されたWebアプリケーションのアクティブユーザセッション一覧を表示するページがあります：

![原文ページ転載](https://ci.apache.org/projects/wicket/guide/7.x/img/tomcat-admin-sessions.png)

Tomcatでは『推定地域』列と『推定ユーザ名』列に表示される値を設定可能です。『Locale』と『userName』という名前のセッション属性を使うのが設定方法の一つになりますが、Wicketの ``Session`` クラスではTomcatの要求する名前そのものの属性を作ることはできないため、素のままの ``HttpSession`` を用いて設定する必要があります：

```java:
HttpSession session = ((ServletWebRequest)RequestCycle.get().
		getRequest()).getContainerRequest().getSession();
session.setAttribute("Locale", "ENGLISH");
session.setAttribute("userName", "Mr BadGuy");
```

### 一時セッションと永続セッション

ユーザがアクセスするのが状態のないページであるならば、Wicketはデータを格納するためのユーザセッションを必要としません。そうした条件下であってもなお、各リクエストを処理するための一時セッションは生成されますが、そうした一時セッションはリクエストの終了と同時に破棄されます。現在のセッションが一時セッションかどうかを調べるには ``isTemporary`` メソッドを使います：

```java:
Session.get().isTemporary();
```

一時セッションでなければ（つまり永続セッションなら）、``getId()`` メソッドで取得できるユニークなIDでもって識別可能です。この値は一時セッションでは null となります。

Wicketは自動的に一時セッションを永続セッションに置き換える必要性を自動的に検知しますが、当初は一時セッションであったものを永続セッションに切り替えるこの処理は、時として手動で制御しなくてはならなくなります。

これを実現するためのシナリオとして、セッション属性を設定する状態のないホームページと、その後にリダイレクトされ、設定されたセッション属性をラベルとして表示するページを持つ BindeSessionExample プロジェクトについて検討してみましょう。２つのページのコードは以下になります：

ホームページ:

```java:
public class HomePage extends WebPage {
    public HomePage(final PageParameters parameters) {
    	Session.get().setAttribute("username", "tommy");
	Session.get().bind();
	setResponsePage(DisplaySessionParameter.class);
    }   
}
```

リダイレクト先:

```java:
public class DisplaySessionParameter extends WebPage {
	public DisplaySessionParameter() {
	   super();
	   add(new Label("username", (String) Session.get().getAttribute("username")));
	}
}
```

繰り返しますが、不必要なコードによる膨張を避けるためにページロジックは極めてシンプルにしています。上記の切り抜きで太字で強調しなくてはならないのは一時セッションを永続セッションに切り替える ``Session`` クラスの ``bind()`` メソッドになります。ホームページでこのメソッドが呼び出されていない場合、リクエストの終わりでセッションは破棄され、リダイレクト先ページでラベルに表示される値は空になります。

### セッションデータの破棄

ユーザがWebアプリケーションの利用を終えた時、ログアウトすることでセッションデータを破棄できるようになっているべきです。現行リクエストの完了後、永続セッションが確実に破棄されるようにするために ``Session`` クラスは ``invalidate()`` メソッドを提供しています。リクエストの完了を待たずに即座にセッションを無効化したい場合には ``invalidateNow()`` メソッドを呼び出すことができます。

``invalidateNow()`` メソッドは即座にあらゆるページ部品（とページ）のインスタンスをセッションから除去する、つまり一度このメソッドを呼び出してしまったら、後の処理ではそれを利用することはできなくなることに注意してください。

### 任意のオブジェクトをメタデータ付きで保存

JavaServer Pages 仕様（第１版） ではページが生成し、アクセス可能な変数に４つのスコープを定義しています：

- リクエスト
  - このスコープで宣言された変数は同一リクエスト内でのみ参照可能です。このスコープの変数の生存期間は（ほとんど）紐付けられたリクエストの生存期間と同じになります。このスコープの変数はすべてのレスポンスが生成されるか、リクエストがどこか別の場所に転送された時点で破棄されます。
- ページ
  - このスコープで宣言された変数は生成したページ内からのみ参照可能です。
- セッション
  - このスコープで宣言された変数は同一セッション内の全てのページにおいて生成と参照が可能です。
- アプリケーション
  - 最も広域のスコープになります。アプリケーション内のあらゆるページから使用できます。（:sos:グローバル変数です。基本的に使っちゃ駄目。）

WicketはJSP仕様を実装しているわけではありませんが（どちらかというと代替物です）、スコープ付き変数と良く似た、ただしより強力なメタデータという機能を持ちます。メタデータはユニークキーと対応する値のペアを格納するJavaのMapと非常によく似ています。Wicketでは次のクラスがそれぞれメタデータを格納できます：``RequestCycle``、``Session``、``Application`` と ``Component``

メタデータのキーには ``org.apache.wicket.MetaDataKey<T>`` クラスのインスタンスを用います。任意のオブジェクトをメタデータに格納するには２引数の ``setMetaData`` メソッドを用います：値を格納するキーと値そのものです。``Session`` や ``Component`` のメタデータを使う場合、Wicketはそれらのインスタンスをシリアライズするため、値はシリアライズ可能でなくてはなりません。この制約はあらゆるオブジェクトを格納できる ``Application`` と ``RequestCycle`` のメタデータには当てはまりません。全てのメタデータにおいて値のデータ型はキーの型パラメータTと互換性のあるものでなくてはなりません。

以前に設定したオブジェクトを取得するには ``getMetaData(MetaDataKey<T> key)`` メソッドを使用します。次の例では アプリケーション内の全てのページから利用できるように ``java.sql.Connection`` オブジェクトを ``Application`` のメタデータに設定しています：

``Application`` クラスのコード：

```java:
public static MetaDataApp extends WebApplication{
	//Do some stuff…
	/**
	* Metadata key definition
	*/
	public static MetaDataKey<Connection> connectionKey = new MetaDataKey<Connection> (){};
	/**
	 * Application's initialization
	 */
	@Override
	public void init(){

		super.init();
		Connection connection;
		//connection initialization…
		setMetaData(connectionKey, connection);
		//Do some other stuff..

	}
}
```

メタデータからオブジェクトを取得するコード：

```java:
Connection connection = Application.get().getMetaData(MetaDataApp.connectionKey);
```

``MetaDataKey<T>`` クラスは抽象クラスのため、サブクラスを実装するか（上の例のように）無名クラスを実装する必要があります。

## 9.5 例外処理

Wicketはアプリケーションの平常運転中にたくさんのカスタム例外を使用しています。ページバージョンが期限切れになった場合に発行される ``PageExpiredException`` については既に見たとおりです。そうした例外の他の例としては ``AuthorizationException`` と ``RestartResponseException`` があります。 これらについては次の章で見ます。レンダリング段階で発生したそれ以外の全ての例外は ``org.apache.wicket.request.IExceptionMapper`` インタフェースの実装、デフォルトでは ``org.apache.wicket.DefaultExceptionMapper`` クラスによって処理されます。**DEVELOPMENT**モードであれば、このマッパーは例外のスタックトレースを表示するページ（ExceptionErrorPage）にリダイレクトします。一方で**DEPLOYMENT**モードで動作中の場合、デフォルトでは org.apache.wicket.markup.html.pages.InternalErrorPage となる内部エラーページを表示します。カスタマイズされた内部エラーページを使用したい場合は次の用にアプリケーション設定を変更します：

```java:
getApplicationSettings().setInternalErrorPage(MyInternalErrorPage.class);
```

ExceptionErrorPageを表示すべきか、内部エラーページを使用すべきか、予期しない例外の場合は全く何も表示しないように
するかを手動で背sっていすることもできます：

```java:
//show default developer page
getExceptionSettings().setUnexpectedExceptionDisplay( ExceptionSettings.SHOW_EXCEPTION_PAGE );
//show internal error page
getExceptionSettings().setUnexpectedExceptionDisplay( ExceptionSettings.SHOW_INTERNAL_ERROR_PAGE );
//show no exception page when an unexpected exception is thrown
getExceptionSettings().setUnexpectedExceptionDisplay( ExceptionSettings.SHOW_NO_EXCEPTION_PAGE );
```

またデフォルトの例外マッパー ``DefaultExceptionMapper`` ではなくカスタマイズした例外マッパーを使う選択肢もあります。その場合は ``Application`` の ``getExceptionMapperProvider`` メソッドをオーバライドする必要があります：

```java:
@Override
public IProvider<IExceptionMapper> getExceptionMapperProvider()
{
    //…
}
```

このメソッドからカスタム例外マッパーを生成する ``org.apache.wicket.util.IProvider`` インタフェースを返却します。

### Ajaxリクエスト

アプリケーションでAjaxリクエストの振る舞いを制御するためには ``org.apache.wicket.settings.ExceptionSettings`` クラスの ``setAjaxErrorHandlingStrategy(ExceptionSettings.AjaxErrorStrategy)`` メソッドを使用することになります。デフォルトではAjaxリクエスト処理中にエラーが発生した場合、設定されたエラーページを描写します。``org.apache.wicket.settings.ExceptionSettings`` クラスのデフォルト戦略を ``AjaxErrorStrategy`` 列挙子の ``INVOKE_FAILURE_HANDLER`` に設定することで、アプリケーションはJavaScriptの onFailure コールバックを呼び出すようになります。

:sos: 原文の時点で色々説明が足りません。

## 9.6 まとめ

この章ではWicketの内部でWebリクエストがどの用に取り扱われるかを見てきました。ほとんどの場合においてこれらの内部処理をカスタマイズする必要は無いのですが、**どのように動作しているかを知ることはフレームワークを100%活用するうえで非常に大切なことです。**

アプリケーションやセッションといった実体は２１章でセキュリティに関する話題に突入した時に役立つことになります。