blob: 7b8407599c77f88d9d908529482fba2a0c6effee [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= NetBeans Eコマースのチュートリアル - セッションの管理
:jbake-type: tutorial
:jbake-tags: tutorials
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: NetBeans Eコマースのチュートリアル - セッションの管理 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, NetBeans Eコマースのチュートリアル - セッションの管理
1. link:intro.html[+概要+]
2. link:design.html[+アプリケーションの設計+]
3. link:setup-dev-environ.html[+開発環境の設定+]
4. link:data-model.html[+データ・モデルの設計+]
5. link:page-views-controller.html[+ページ・ビューおよびコントローラ・サーブレットの準備+]
6. link:connect-db.html[+データベースへのアプリケーションの接続+]
7. link:entity-session.html[+エンティティ・クラスおよびセッションBeanの追加+]
8. *セッションの管理*
9. link:transaction.html[+トランザクション・ビジネス・ロジックの統合+]
10. link:language.html[+言語サポートの追加+]
11. link:security.html[+アプリケーションの保護+]
12. link:test-profile.html[+テストとプロファイリング+]
13. link:conclusion.html[+まとめ+]
image::../../../../images_www/articles/68/netbeans-stamp-68-69.png[title="このページの内容は、NetBeans IDEバージョン6.8および6.9に適用されます"]
なんらかのショッピング・カート機能を提供するすべてのEコマース・アプリケーションでは、ユーザーがWebサイトの中でクリックしたときにユーザー固有のデータを覚える必要があります。開発者にとって残念ですが、インターネットの通信で使用されるHTTPプロトコルは_ステートレス_なプロトコルです。サーバーが受け取る各リクエストは、以前に受け取ったリクエストとは無関係の独立した情報です。このため、顧客がボタンをクリックして自分のショッピング・カートに項目を追加したら、アプリケーションはこのユーザーのカートの状態を更新するのみでなく、このアクションが、同時にサイトをブラウズしているユーザーのカートに影響しないようにする必要があります。
上記のシナリオを適切に処理するには、ユーザーがサイトにアクセスしている間、_セッション_を作成し、セッションを維持できるようにする機能を実装する必要があります。このために、すべてのJavaベースのWebアプリケーションの基盤であるサーブレット・テクノロジにはlink:http://java.sun.com/javaee/6/docs/api/javax/servlet/http/HttpSession.html[+`HttpSession`+]インタフェースが用意されています。また、セッションが維持されている間、アプリケーションが一時的にユーザー・データを格納できるようにする`ShoppingCart`および`ShoppingCartItem`というクラスを定義する必要があります。
このチュートリアル・ユニットでは、他のNetBeans Eコマースのチュートリアルとは異なる方法を取ります。プロジェクト・ファイルを作成してそのプロジェクトにコピーして貼付けできるコード・スニペットを提供するという手順に従うのではなく、このユニットの完成したプロジェクトのスナップショットを開き、IDEデバッガやその他のツールを使用してコードを調べます。このプロセスの中で、`HttpSession`オブジェクトをコードに適用して、Webサイトへのアクセスごとに専用のセッションを作成する方法を学習します。また、_スコープ指定された変数_について、およびそれをJavaクラスやJSPページで使用する方法についても学習します。このユニットでは、セッションを維持するための`HttpSession`のデフォルト機構(Cookie)についても説明し、ユーザーのブラウザでCookieが非アクティブ化されている場合に必要となる手順を示します。ユニットの最後ではセッション・タイム・アウトについても触れ、リクエストをインターセプトしてセッションが存在するかどうかを確認する単純なフィルタを作成して、セッション・タイム・アウトを処理する方法を示します。
このチュートリアルでビルドするアプリケーションのライブ・デモを、link:http://dot.netbeans.org:8080/AffableBean/[+NetBeans Eコマースのチュートリアルのデモ・アプリケーション+]で表示できます。
|===
|ソフトウェアまたはリソース |必須バージョン
|link:https://netbeans.org/downloads/index.html[+NetBeans IDE+] |Javaバンドル版、6.8または6.9
|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Java Development Kit (JDK)+] |バージョン6
|<<glassFish,GlassFishサーバー>> |v3またはOpen Source Edition 3.0.1
|link:http://dev.mysql.com/downloads/mysql/[+MySQLデータベース・サーバー+] |バージョン5.1
|link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot5.zip[+AffableBeanプロジェクト+] |スナップショット5
|===
*注意: *
* NetBeans IDEが正常に動作するには、JDK (Java Development Kit)が必要です。上記に一覧表示されているいずれのリソースもインストールされていない場合は、最初にJDKをダウンロードしてインストールするようにしてください。
* NetBeans IDEJavaバンドル版には、このチュートリアルでビルドするアプリケーションに必要なJava WebおよびEEテクノロジが含まれています。
* NetBeans IDEJavaバンドル版には、このチュートリアルに必要なGlassFishサーバーも含まれています。link:https://glassfish.dev.java.net/public/downloadsindex.html[+GlassFishサーバーを別個にダウンロードする+]こともできますが、NetBeansダウンロードに付属するバージョンを使用すると、IDEに自動的に登録されるので便利です。
* このチュートリアル・ユニットは、以前のユニットを完了させていなくても進めることができます。そのために、データベースの準備や、IDEGlassFishおよびMySQL間の接続の確立について説明したlink:setup.html[+設定手順+]を参照してください。
[[session-data]]
== セッション・データの処理
アプリケーションは、`HttpSession`オブジェクトを使用してユーザー・セッションを管理できます。ユーザー固有のデータを`HttpSession`オブジェクトにバインドして、後でこのデータにアクセスできます。バインドとアクセスの両方のアクションは、Javaクラスから行うことも、EL式のセッション・スコープ指定された変数から行うことも可能です。
* <<httpSession,HttpSessionオブジェクトの操作>>
* <<scopedVariables,Webアプリケーションでのスコープ指定された変数の操作>>
[[httpSession]]
=== HttpSessionオブジェクトの操作
`AffableBean`アプリケーションは`HttpSession`オブジェクトを使用して、複数のリクエストにわたってユーザーを識別します。`HttpSession`オブジェクトを取得するには、特定のリクエストで次のように`getSession()`を使用します。
[source,java]
----
HttpSession session = request.getSession();
----
そのリクエストのセッション・オブジェクトがまだ存在しない場合、このメソッドは新しいセッションを作成して返します。
セッション・オブジェクトは、リクエスト間でデータを渡すための輸送手段として使用できます。オブジェクトをセッションにバインドするには、`setAttribute`メソッドを使用します。同様に、オブジェクトをセッションから取得するには、`getAttribute`を使用します。たとえば`AffableBean`アプリケーションでは、次の方法でユーザーのショッピング・カートが作成され、ユーザー・セッションにバインドされます。
[source,java]
----
ShoppingCart cart = new ShoppingCart();
session.setAttribute("cart", cart);
----
セッションからカートを取得するために、次のように`getAttribute`メソッドが適用されます。
[source,java]
----
cart = (ShoppingCart) session.getAttribute("cart");
----
JSPページでは、セッションにバインドされているオブジェクトにEL式を使用してアクセスできます。上記の例を続けて使用します。「`cart`」という名前の`ShoppingCart`オブジェクトがセッションにバインドされている場合、次のEL式を使用することでこのオブジェクトにアクセスできます。
[source,java]
----
${cart}
----
しかし、`ShoppingCart`オブジェクト自体にアクセスしてもほとんど意味がありません。本当に必要なのは、オブジェクトに格納されている値にアクセスするための方法です。プロジェクトのスナップショットで新しい`ShoppingCart`クラスを調べると、次のプロパティが含まれていることがわかります。
* `double total`
* `int numberOfItems`
* `List<String, ShoppingCartItem> items`
プロパティに対応する取得メソッドがあれば、EL式で単純なドット表記法を使用して個々のプロパティの値にアクセスできます。`cart.jsp`ページを調べると、次のように、ちょうどこの方法で`numberOfItems`の値にアクセスしているのが確認できます。
[source,html]
----
<p>Your shopping cart contains ${cart.numberOfItems} items.</p>
----
上記の`items`リストのような複数の値を含むプロパティからデータを抽出するために、`cart.jsp`ページでは次のように`<c:forEach>`ループを使用しています。
[source,xml]
----
<c:forEach var="cartItem" items="${cart.items}" varStatus="iter">
<c:set var="product" value="${cartItem.product}"/>
<tr class="${((iter.index % 2) == 0) ? 'lightBlue' : 'white'}">
<td>
<img src="${initParam.productImagePath}${product.name}.png"
alt="${product.name}">
</td>
<td>${product.name}</td>
<td>
&amp;euro; ${cartItem.total}
<br>
<span class="smallText">( &amp;euro; ${product.price} / unit )</span>
</td>
...
</tr>
</c:forEach>
----
`ShoppingCartItem``product`プロパティは、カート項目の製品タイプを識別します。上記のループでは、最初に`product`変数を`${cartItem.product}`の式に設定することで、これを利用しています。その後、この変数を使用して、名前や価格などの製品に関する情報を取得しています。
[[scopedVariables]]
=== Webアプリケーションでのスコープ指定された変数の操作
JSP/サーブレット・テクノロジを扱う場合、アプリケーションのレルム内で使用できる4つのスコープ・オブジェクトがあります。JSPテクノロジには、サーブレットAPIによって定義されるクラスにアクセスできる_暗黙オブジェクト_が実装されています。
|===
|スコープ |定義 |サーブレット・クラス |JSP暗黙オブジェクト
|*アプリケーション* |Webアプリケーションのグローバル・メモリー |`link:http://java.sun.com/javaee/6/docs/api/javax/servlet/ServletContext.html[+javax.servlet.ServletContext+]` |`applicationScope`
|*セッション* |ユーザー・セッションに固有のデータ |`link:http://java.sun.com/javaee/6/docs/api/javax/servlet/http/HttpSession.html[+javax.servlet.http.HttpSession+]` |`sessionScope`
|*リクエスト* |個々のサーバー・リクエストに固有のデータ |`link:http://java.sun.com/javaee/6/docs/api/javax/servlet/http/HttpServletRequest.html[+javax.servlet.HttpServletRequest+]` |`requestScope`
|*ページ* |単一のページ(JSPのみ)のコンテキストのみで有効なデータ |`[n/a]` |`pageScope`
|===
エディタでプロジェクトの`category.jsp`ファイルを開くと、EL式に`${categories}``${selectedCategory}`および`${categoryProducts}`などの様々なスコープ指定された変数が含まれているのが確認できます。`${categories}`変数はアプリケーション・スコープ指定されており、次のように`ControllerServlet``init`メソッドで設定されています。
[source,java]
----
// store category list in servlet context
getServletContext().setAttribute("categories", categoryFacade.findAll());
----
他の`${selectedCategory}``${categoryProducts}`2つは、`ControllerServlet`からアプリケーションのセッション・スコープに置かれています。例:
[source,java]
----
// place selected category in session scope
session.setAttribute("selectedCategory", selectedCategory);
----
*注意: *前のチュートリアル・ユニットから続けている場合、`${selectedCategory}``${categoryProducts}`はもともとリクエスト・スコープ内に置かれていたことに気付くかもしれません。前のユニットではこれで問題ありませんでしたが、ここではユーザーがカテゴリ・ページで「add to cart」ボタンをクリックしたらどうなるかを考えてください。サーバーは、現在表示されているカテゴリ・ページを返すことによって、`addToCart`リクエストに応答します。したがって、選択されたカテゴリに関係する`selectedCategory``categoryProducts`を知る必要があります。この情報は、リクエストごとに確立するのではなく、複数のリクエストにまたがって保持し、必要なときにアクセスできるように、`category`リクエストからセッション・スコープに置きます。また、カート・ページによって提供される機能を調べます。(機能については<<cartPage,後で>>説明します。)「continue shopping」ボタンを押すと、ユーザーは前に表示されていたカテゴリに戻ります。再度`selectedCategory`変数と`categoryProducts`変数が必要です。
EL式でスコープ指定された変数を参照する場合、(異なるスコープに同じ名前の2つの変数がないと仮定して)変数のスコープを指定する必要はありません。JSPエンジンは4つすべてのスコープをチェックして、最初に一致した変数を返します。たとえば、`category.jsp`にある次の式を見てください。
[source,java]
----
${categoryProducts}
----
これは、次の式の短縮形です。
[source,java]
----
${sessionScope.categoryProducts}
----
[tips]#詳細は、次のリソースを参照してください。#
* link:http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/web-tier/web-tier5.html#1079198[+J2EEプラットフォームでのエンタープライズ・アプリケーションの設計: 状態スコープ+]
* link:http://download.oracle.com/docs/cd/E17477_01/javaee/5/tutorial/doc/bnafo.html[+情報の共有 > スコープ指定されたオブジェクトの使用+]
* link:http://download.oracle.com/docs/cd/E17477_01/javaee/5/tutorial/doc/bnahq.html#bnaij[+統一された式言語 > 暗黙的なオブジェクト+]
[[debug]]
== Javaデバッガによるセッション・データの確認
アプリケーションが実行時にどのように動作するかを調べます。IDEのデバッガを使用してコードをステップ実行し、`HttpSession`がどのように作成されるのか、また、後で取得できるように他のオブジェクトをセッション・スコープに置く方法を調べます。
1. このチュートリアル・ユニットのlink:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot5.zip[+プロジェクト・スナップショット+]をIDEで開きます。「プロジェクトを開く」(image::images/open-project-btn.png[])ボタンをクリックし、ウィザードを使用して、このプロジェクトをダウンロードしたコンピュータ上の場所に移動します。link:entity-session.html[+前のチュートリアル・ユニット+]から続けている場合は、このプロジェクト・スナップショットに`ShoppingCart`クラスと`ShoppingCartItem`クラスを含む新しい`cart`パッケージが含まれています。また、次のファイルも変更されています。
* `WEB-INF/web.xml`
* `css/affablebean.css`
* `WEB-INF/jspf/header.jspf`
* `WEB-INF/jspf/footer.jspf`
* `WEB-INF/view/cart.jsp`
* `WEB-INF/view/category.jsp`
* `WEB-INF/view/checkout.jsp`
* `controller/ControllerServlet`
[start=2]
. プロジェクトを実行(image::images/run-project-btn.png[])して、使用しているデータベースとアプリケーション・サーバーで適切に構成されていることを確認します。
プロジェクトの実行時にエラーが発生した場合は、データベースの準備や、IDEGlassFishおよびMySQL間の接続の確立について説明したlink:setup.html[+設定手順+]をもう一度確認してください。
[start=3]
. ブラウザでアプリケーションの機能をテストします。link:entity-session.html[+前のチュートリアル・ユニット+]から直接継続している場合は、次のような機能拡張に気付くでしょう。
=== カテゴリ・ページ
* 初めて「add to cart」をクリックするとショッピング・カートが有効になり、「proceed to checkout」ウィジェットがヘッダーに表示されます。
* add to cart」をクリックすると、ヘッダーにあるショッピング・カート・ウィジェットのカート項目の数が更新されます。
* view cart」をクリックすると、カート・ページが表示されます。
* proceed to checkout」をクリックすると、チェックアウト・ページが表示されます。
image::images/category-page.png[title="ショッピング・カート機能があるカテゴリ・ページ"]
[[cartPage]]
=== カート・ページ
* clear cart」をクリックすると、ショッピング・カートの項目が空になります。
* continue shopping」をクリックすると、前に表示されていたカテゴリ・ページに戻ります。
* proceed to checkout」をクリックすると、チェックアウト・ページが表示されます。
* 項目の「quantity」フィールドに1から99までの数字を入力してから「update」をクリックすると、項目の合計と小計が再計算されます。
* 項目の「quantity」フィールドに0を入力してから「update」をクリックすると、表示された表からその項目が除去されます。
image::images/cart-page.png[title="ショッピング・カート機能があるカート・ページ"]
=== チェックアウト・ページ
* view cart」をクリックすると、カート・ページが表示されます。
* submit purchase」をクリックすると、(ユーザー固有のデータなしで)確認ページが表示されます。
image::images/checkout-page.png[title="ショッピング・カート機能があるチェックアウト・ページ"]
[start=4]
. 「ファイルに移動」ダイアログを使用して、エディタで`ControllerServlet`を開きます。[Alt]-[Shift]-[O] (Macの場合は[Ctrl]-[Shift]-[O])を押してから、ダイアログで「`Controller`」と入力して「OK」をクリックします。
image::images/go-to-file-dialog.png[title="「ファイルに移動」ダイアログを使用した、エディタへのプロジェクト・リソースの速やかな表示"]
[start=5]
. `HttpSession`オブジェクトを作成する行(150行目)の`doPost`メソッドにブレークポイントを設定します。ブレークポイントを設定するには、エディタの左マージンをクリックします。
image::images/breakpoint.png[title="エディタの左マージンのクリックによるブレークポイントの設定"]
エディタの行番号表示を切り替えるには、左マージンを右クリックして「行番号を表示」を選択します。
[start=6]
. デバッガを実行します。IDEのメイン・ツールバーにある「プロジェクトをデバッグ」(image::images/debug-project-btn.png[])ボタンをクリックします。GlassFishサーバーが起動(すでに実行中の場合は再起動)し、そのデバッグ・ポート番号でソケットを開きます。アプリケーションの開始ページがブラウザで開きます。
デバッグ・ポート番号は、「サーバー」ウィンドウ(「ツール」>「サーバー」)から表示および変更できます。使用しているサーバーの「Java」タブを選択します。「デバッグ設定」の下の「使用するアドレス」フィールドにポート番号を指定します。
[start=7]
. アプリケーションの開始ページがブラウザに表示されたら、いずれかのカテゴリ・イメージをクリックしてカテゴリ・ページに移動します。「add to cart」ボタンをクリックすると、次のようにサーバーに`addToCart`リクエストが送信されることを思い出してください。
[source,java]
----
<form action="addToCart" method="post">
----
link:page-views-controller.html#controller[+ページ・ビューおよびコントローラ・サーブレットの準備+]で説明したように、`ControllerServlet`の`doPost`メソッドは、`/addToCart`のURLパターンのリクエストを処理します。このため、ユーザーが「add to cart」ボタンをクリックすると`doPost`メソッドがコールされることを想定できます。
[start=8]
. カテゴリ・ページで、いずれかのカテゴリの「add to cart」をクリックします。IDEに戻ると、デバッガがブレークポイントで一時停止されていることがわかります。
image::images/breakpoint-suspended.png[title="エディタのブレークポイントで一時停止されたデバッガ"]
[start=9]
. `getSession()`へのコールにカーソルを置き、[Ctrl]-[Space]を押してJavadocドキュメントを呼び出します。
image::images/javadoc-getsession.png[title="[Ctrl]-[Space]の押下によるJavadocドキュメントの呼出し"]
ドキュメントによると、`getSession()`は現時点でリクエストに関連付けられている`HttpSession`を返し、セッションが存在しない場合、このメソッドは新しいセッション・オブジェクトを作成します。
=== IDEJavadocサポートの利用
IDEには、Java EE開発向けのJavadocが組み込まれています。IDEにはJava EE 6 API仕様がバンドルされており、「ヘルプ」>「Javadoc参照」>「Java EE 6」を選択して外部ブラウザで開けます。
IDEにはこの他にも、APIドキュメントに簡単にアクセスできる、次のような様々な機能が含まれています。
* *Javadocウィンドウ:* 「ウィンドウ」>「その他」>「Javadoc」を選択します。JavadocウィンドウはIDEの下部領域で開き、エディタのカーソル位置に関連するAPIドキュメントを表示します。
* *Javadoc索引検索:* 「ヘルプ」>「Javadoc索引検索」([Shift]-[F1]、Macの場合は[fn]-[Shift]-[F1])を選択します。探しているクラス名を入力してから、一覧表示された結果からクラスを選択します。ウィンドウの下部のペインに、API仕様からクラスの完全な説明が表示されます。
* *エディタのドキュメント・ポップアップ:* エディタの特定の要素で[Ctrl]-[Space]を押すと、Javadocドキュメントがポップアップ・ウィンドウに表示されます。「外部ブラウザ」(image::images/external-browser-btn.png[])ボタンをクリックすると、ブラウザでドキュメントが開きます。[Ctrl]-[Space]をコード補完のためにのみ使用する場合は、「ツール」>「オプション」(Macの場合は「NetBeans」>「プリファレンス」)で「オプション」ウィンドウを開いてから「エディタ」>「コード補完」を選択して、ドキュメント・ポップアップを非アクティブ化できます。「ドキュメント・ウィンドウを自動ポップアップ」オプションを選択解除します。
独自の作業をドキュメント化する場合、作成したクラスおよびメソッドにJavadocコメントを追加することを検討してください。`ShoppingCart`クラスを開き、クラス・メソッドに追加されているJavadocコメントを確認します。Javadocコメントは、`/** ... */`区切り文字でマークされています。たとえば、`addItem`メソッドには、メソッドの署名の前に次のようなコメントが入っています。
[source,xml]
----
/**
* Adds a <code>ShoppingCartItem</code> to the <code>ShoppingCart</code>'s
* <code>items</code> list. If item of the specified <code>product</code>
* already exists in shopping cart list, the quantity of that item is
* incremented.
*
* @param product the <code>Product</code> that defines the type of shopping cart item
* @see ShoppingCartItem
*/
public synchronized void addItem(Product product) {
----
これによって開発者(およびプロジェクトにかかわる他の人)が、メソッドについてのJavadocドキュメントを表示できるようになります。これを示すために、[Ctrl]-[7] (Macの場合は[⌘]-[7])で「ナビゲータ」を開き、カーソルを`addItem`メソッドの上に移動します。
image::images/javadoc-additem.png[title="ナビゲータでのメソッド上へのカーソルの移動によるJavadocドキュメントの表示"]
IDEを使用して、一連のJavadoc HTMLページを生成することもできます。「プロジェクト」ウィンドウでプロジェクト・ノードを右クリックし、「Javadocを生成」を選択します。IDEによって、プロジェクトのディレクトリの`dist/javadoc`フォルダにJavadocが生成され、ブラウザにインデックス・ページが表示されます。
Javadocの詳細は、次のリソースを参照してください。
* link:http://java.sun.com/j2se/javadoc/[+Javadocツールの公式ホーム・ページ+]
* link:http://java.sun.com/j2se/javadoc/writingdoccomments/index.html[+Javadocツールのドキュメント・コメントを書く方法+]
[start=10]
. `session`変数の上にカーソルを移動します。デバッガは、_それが実行しようとしている_行で一時停止されています。`getSession()`によって返される値は、この時点では`session`変数に保存されておらず、ポップアップには「"`session`"は、現在のコンテキスト内で既知の変数ではありません。」と表示されます。
image::images/session-variable.png[title="変数および式へのカーソルの移動による現在の値の判定"]
[start=11]
. エディタの上にあるデバッガ・ツールバーの「ステップ・オーバー」(image::images/step-over-btn.png[])ボタンをクリックします。この行が実行され、デバッガはファイルの次の行に進みます。
[start=12]
. 再度`session`変数の上にカーソルを移動します。今度は、`session`変数に現在設定されている値を確認します。
[.feature]
--
image:images/session-variable-set.png[role="left", link="images/session-variable-set.png"]
--
NetBeans 6.9では、ポップアップでグレーのポインタ(image::images/grey-pointer.png[])をクリックすると、強調表示された要素に含まれている変数の値の一覧を展開できます。
[start=13]
. 「ステップ・オーバー」(image::images/step-over-btn.png[])ボタン([F8]、Macの場合は[fn]-[F8])をクリックして`if`文(154行目)に入ります。ブラウザで「add to cart」ボタンをクリックしたばかりなので、`userPath.equals("/addToCart")`の式は`true`として評価されるはずです。
[start=14]
. [Ctrl]を押しながらマウスでクリックして`userPath.equals("/addToCart")`の式を強調表示します。今度は、強調表示した式の値を示すポップアップが表示されます。
image::images/expression.png[title="式の強調表示による現在の値の判定"]
[start=15]
. [F8] (Macの場合は[fn]-[F8])を押して次の行(158行目)に進みます。このアプリケーションは、ユーザーが初めてカートに項目を追加するときにのみユーザー・セッションの`ShoppingCart`オブジェクトを作成するように設計されています。このデバッグ・セッションで`addToCart`リクエストが受け取られたのはこれが最初であるため、`cart`オブジェクトは`null`と等しいと想定できます。
image::images/cart-null.png[title="Cartオブジェクトはユーザーがショッピング・カートに項目を追加するまで存在しない"]
[start=16]
. [F8] (Macの場合は[fn]-[F8])を押して次の行(160行目)に進みます。次に、`ShoppingCart`オブジェクトが作成される160行目で、「ステップ・イン」(image::images/step-into-btn.png[])ボタンをクリックします。コールされるメソッドにデバッガがステップ・インします。この場合、直接`ShoppingCart`のコンストラクタに移動します。
image::images/cart-constructor.png[title="メソッドにステップ・インして実行時に他のクラスでの実行を追跡する"]
[start=17]
. [Ctrl]-[Tab]を押して`ControllerServlet`に戻ります。「コール・スタック」(image::images/call-stack-badge.png[])バッジが160行目に表示されます(現在、デバッガがコール・スタックの上位にあるいずれかのメソッドで一時停止されていることが表示されています)。
[Alt]-[Shift]-[3] (Macの場合は[Ctrl]-[Shift]-[3])を押すとIDEの「コール・スタック」ウィンドウが開きます。
[start=18]
. [F8] (Macの場合は[fn]-[F8])を押して、コードの実行を進めます。デバッガが`ShoppingCart`コンストラクタを完了すると、`ControllerServlet`に戻ります。
`ControllerServlet`161行目では、新しく作成された`cart`オブジェクトをセッションにバインドします。
[source,java]
----
session.setAttribute("cart", cart);
----
これを確認するには、デバッガの「変数」ウィンドウを開きます。「ウィンドウ」>「デバッグ」>「変数」を選択するか、[Alt]-[Shift]-[1] (Macの場合は[Ctrl]-[Shift]-[1])を押します。
[.feature]
--
image:images/variables-win-session.png[role="left", link="images/variables-win-session.png"]
--
session」>「session」>「attributes」ノードを展開すると、セッションにバインドされているオブジェクトを表示できます。上記のイメージでは、現時点でセッションにバインドされている(強調表示された) 2つの項目があります。これらは、それぞれ`ControllerServlet`83行目と89行目でインスタンス化された`selectedCategory``categoryProducts`です。これらの項目は両方とも、以前カテゴリ・イメージをクリックして`ControllerServlet`がカテゴリ・ページのリクエストを処理したときにバインドされました。
[start=19]
. [F8] (Macの場合は[fn]-[F8])を押して161行目を実行します。`cart`オブジェクトはセッションにバインドされており、「変数」ウィンドウは変更を反映して更新されます。「変数」ウィンドウでは、現在、セッションに3つの属性が含まれていることを確認できます。3つ目の変数は、新しく初期化された`ShoppingCart`オブジェクト(次で強調表示)です。
[.feature]
--
image:images/variables-win-session-cart.png[role="left", link="images/variables-win-session-cart.png"]
--
これまでは、「変数」ウィンドウに一覧表示されたセッションが`HttpSession`セッションを示していることを「証明」しませんでした。前述のように、`HttpSession`は実際にはインタフェースであるため、`HttpSession`オブジェクト(セッション・オブジェクト)について説明する場合、実際には`HttpSession`インタフェースを実装するオブジェクトを指しています。「変数」ウィンドウで「`session`」の上にカーソルを移動すると、この変数が`HttpSession`オブジェクトを表していることを示すポップアップが表示されます。表示されているように、`StandardSessionFacade`型は、GlassFish`HttpSession`インタフェースを実装するために使用する内部クラスです。Tomcatに詳しいユーザーが「値」列に表示された「`org.apache.catalina`」のパスに戸惑うのは、GlassFish Web/サーブレット・コンテナが実はApache Tomcatコンテナの派生クラスであるためです。
新しい`ShoppingCart`がセッションに追加され、リクエストの処理が続行されます。「add to cart」機能の実装を完成させるために、次のアクションが取られます。
* 選択された製品のIDがリクエストから取得される(165行目)
* IDを使用して`Product`オブジェクトが作成される(169行目)
* `product`を使用して新しい`ShoppingCartItem`が作成される(170行目)
* `ShoppingCartItem``ShoppingCart``items`リストに追加される(170行目)
[start=20]
. 上記で一覧表示された4つのアクションを意識しながら、[F8] (Macの場合は[fn]-[F8])を押して、コードの実行を進めます。デバッガが170行目で一時停止したら一時休止します。
[start=21]
. セッションにウォッチを作成します。これによって、次の手順で`addItem`メソッドにステップ・インするときに、セッションに含まれている値を表示できるようになります。「変数」ウィンドウでセッションを右クリックして、「固定ウォッチを作成」を選択します。
image::images/create-watch.png[title="デバッグ・セッションでのコードのステップ実行による変数のウォッチの作成"]
または、エディタ内の`session`変数にカーソルを置いてから、右クリックして「新規ウォッチ」を選択します。「新規ウォッチ」ダイアログでは、アプリケーションのデバッグ時に継続的に監視する変数または式を指定できます。(式の場合は、最初に式を強調表示してから、右クリックして「新規ウォッチ」を選択します。)
image::images/new-watch-dialog.png[title="エディタ内で変数や式を右クリックして「新規ウォッチ」を選択する"]
`session`変数とそれに含まれるすべての変数の新しいウォッチが作成されます。ウォッチは、「ウォッチ」ウィンドウ(「ウィンドウ」>「デバッグ」>「ウォッチ」)から表示するか、「変数」ウィンドウの左マージンにある「ウォッチ」(image::images/watch-btn.png[])ボタンを切り替えて「変数」ウィンドウの最初の行に表示可能です。
コードをステップ実行しながら、デバッガで変数を監視できるようになります。これは、たとえば特定の変数の値をたどる場合(そして各手順で「変数」ウィンドウに示される全リストから選択しなくても済むようにする場合)や、調べる必要のある変数が含まれていないクラスに一時的にステップ・インする場合に役立ちます。
[start=22]
. 「ステップ・イン」(image::images/step-into-btn.png[])ボタンをクリックして、`ShoppingCart``addItem`メソッドにステップ・インします。
[start=23]
. 53行目まで`addItem`メソッドをステップ実行します。Javadocに記述されているとおり、`addItem`_`ShoppingCart``items`リストに`ShoppingCartItem`を追加します。指定された`product`の項目がすでにショッピング・カート・リストに存在する場合、その項目の数量が増加します。」_
[start=24]
. (上記の<<step21,ステップ21>>で)ウォッチを作成した`session`変数を調べます。51行目の`items.add(scItem)`文によって、`ShoppingCart``items`リストに新しい`ShoppingCartItem`が追加されました。これは、セッションに含まれている3つ目の属性(`cart`変数)を調べるとわかります。
[.feature]
--
image:images/variables-window-add-item.png[role="left", link="images/variables-window-add-item.png"]
--
この段階で、リクエストのために`HttpSession`が作成される方法、`ShoppingCart`オブジェクトが作成されてセッションにアタッチされる方法、および`ShoppingCartItem`がユーザーの製品選択に基づいて作成され、`ShoppingCart``items`のリストに追加される方法を確認できます。残っているアクションは、`category.jsp`ビューへのリクエストの転送のみです。
[start=25]
. エディタでJSPフラグメント(`header.jspf`)を開き、86行目にブレークポイントを設定します。この行には、カート項目の数を表示する、ショッピング・カート・ウィジェット内のEL文が含まれています。
image::images/breakpoint-jsp.png[title="JSPページでデバッガを一時停止できる"]
[start=26]
. デバッガ・ツールバーの「続行」(image::images/continue-btn.png[])ボタンをクリックします。デバッガは実行が完了するか、別のブレークポイントに達するまで続行されます。この場合、デバッガはヘッダーのJSPフラグメントの86行目で一時停止されます。
*注意:* JSPページでデバッガを一時停止させるには、ブレークポイントを設定する必要があります。たとえば、`ControllerServlet`がリクエストを適切なビューに転送したとき、デバッガはJSPページ内で自動的に一時停止されません。
[start=27]
. まだ開いていない場合は「変数」ウィンドウを開きます([Alt]-[Shift]-[1]、Macの場合は[Ctrl]-[Shift]-[1])。Javaクラスとは異なり、JSPページではデバッガで変数や式の上にカーソルを移動してもツールチップは表示_されません_。ただし、コードをステップ実行しながら、「変数」ウィンドウで変数の値を判定できます。では、`${cart.numberOfItems}`の値はどこにあるでしょうか。
[start=28]
. 「変数」ウィンドウで、「暗黙的なオブジェクト」>「pageContext」>「session」>「session」>「attributes」ノードを展開します。これによって、前に`ControllerServlet`をデバッグしていたときのように、セッション・オブジェクトにアクセスできるようになります。上記のステップ21でウォッチを作成したセッションは、実は同じオブジェクトを指しています。ここで、`${cart.numberOfItems}`の値が「`1`」と等しいことを確認できます。
[.feature]
--
image:images/variables-window-number-of-items.png[role="left", link="images/variables-window-number-of-items.png"]
--
「変数」ウィンドウなどのIDEのウィンドウは、いずれもウィンドウのヘッダーを右クリックしてから「ウィンドウを最大化」([Shift]-[Esc])を選択することで最大化できます。
デバッガで、`pageContext`の暗黙オブジェクトにアクセスできるようになります。`pageContext`JSPページのコンテキストを示しており、`HttpServletRequest``HttpSession``ServletContext`オブジェクトなどの様々なオブジェクトへの直接的なアクセスを提供します。詳細は、link:http://java.sun.com/javaee/5/docs/tutorial/doc/bnahq.html#bnaij[+Java EE 5チュートリアル: 暗黙オブジェクト+]を参照してください。
[start=29]
. セッションの終了(image::images/finish-session-btn.png[])ボタンをクリックします。ランタイムが実行を完了し、デバッグ・セッションが終了します。ブラウザにカテゴリ・ページが完全にレンダリングされ、ページ・ヘッダーにあるショッピング・カート・ウィジェットに1つの項目が含まれているのが確認できます。
IDEデバッガは、プロジェクトが想定どおりに動作しない場合の検査用としてのみでなく、コードに詳しくなるためのツールとしても便利であることがわかるでしょう。他にも、デバッガ・ツールバーには次のような便利なボタンがあります。
* (image::images/step-out.png[])*ステップ・アウト:* 現在のメソッド・コールをステップ・アウトします。呼出しスタックの最上位のメソッド・コールを実行して除去します。
* (image::images/run-to-cursor.png[])*カーソルまで実行:* カーソルが置かれている行まで実行します。
* (image::images/apply-code-changes.png[])*コードの変更を適用:* ファイルを編集してからこのボタンを押すと、ファイルが再コンパイルされ、変更がデバッグ・セッションに反映されます。
* (image::images/step-over-expression.png[])*式をステップ・オーバー:* 式のメソッド・コールごとの入力パラメータおよび結果の出力値を表示できるようにします。「ローカル変数」ウィンドウで、前のメソッドの出力値と次のメソッドの入力パラメータを検査できます。次のメソッド・コールがない場合、「式をステップ・オーバー」は「ステップ・オーバー」(image::images/step-over-btn.png[])コマンドと同様に機能します。
[[session-track]]
== セッション・トラック・オプションの確認
クライアントとサーバー間のセッションをトラックするための慣習的な方法が3つあります。圧倒的に多く使用されているのはCookieです。URL書換えは、Cookieがサポートされていないか無効になっている場合に適用できます。隠しフォーム・フィールドも、複数のリクエストにわたって「状態を維持する」ための手段として使用できますが、これらはフォーム内での使用に制限されます。
`AffableBean`プロジェクトでは、カテゴリとカートの両方のページに隠しフィールド・メソッドの例が含まれています。製品項目用に表示する「add to cart」および「update」ボタンには、ボタンがクリックされると製品IDをサーバーに渡す隠しフィールドが含まれています。エディタで`cart.jsp`ページを開くと、`<form>`タグに隠しフィールドが含まれているのが確認できます。
[source,xml]
----
<form action="updateCart" method="post">
*<input type="hidden"
name="productId"
value="${product.id}">*
...
</form>
----
このように、製品IDはリクエスト・パラメータとして送信されます。この製品IDは、ユーザーのカート内の項目の数量を変更する必要があるときに、項目を識別するためにサーバーによって使用されます。
サーブレットAPIは、セッションを管理するための高水準な機構を提供します。基本的にこの機構では、リクエストとレスポンスのサイクルごとにクライアントとサーバー間でCookieを作成して渡します。クライアントのブラウザでCookieが使用できない場合、サーブレット・エンジンは自動的にURL書換えに戻ります。次の2つの課題でこの機能を示します。
* <<http-monitor,HTTPモニターによるクライアントとサーバー間の通信の確認>>
* <<url-rewrite,URL書換えによるセッションの維持>>
[[http-monitor]]
=== HTTPモニターによるクライアントとサーバー間の通信の確認
デフォルトでは、サーブレット・エンジンは、Cookieを使用してリクエスト間のセッションを維持および識別します。セッション・オブジェクトごとにランダムな英数字が生成され、一意の識別子として使用されます。この識別子は、「`JSESSIONID`Cookieとしてクライアントに渡されます。クライアントがリクエストを作成すると、サーブレット・エンジンは`JSESSIONID` Cookieの値を読み取り、そのリクエストが属しているセッションを判定します。
これを示すために、IDEHTTPモニターと連携してデバッガを使用します。
1. まず、使用しているサーバーのHTTPモニターをアクティブ化します。「ツール」>「サーバー」を選択します。「サーバー」ウィンドウの左の列で、使用しているサーバー(GlassFish)を選択します。次にメインの列で、「HTTPモニターを有効化」オプションを選択します。
image::images/servers-win-http-monitor.png[title="「HTTPモニターを有効化」オプションの選択によるHTTPモニターのアクティブ化"]
[start=2]
. サーバーがすでに実行されている場合は再起動する必要があります。しかし、ここではデバッガを使用する予定であり、デバッガを実行すると異なるポートで通信するためにサーバーが再起動されるため、単にIDEのメイン・ツールバーにある「プロジェクトをデバッグ」(image::images/debug-project-btn.png[])ボタンをクリックします。サーバーが再起動し、デバッグ・セッションが開始して、アプリケーションの開始ページがブラウザで開きます。HTTPモニターがIDEの最下部の領域に表示されます。
image::images/http-monitor.png[title="HTTPモニターがデフォルトでIDEの最下部の領域に表示される"]
[start=3]
. 上記のイメージで示したように、左の列のAffableBeanレコードをクリックします。左の列でレコードを選択すると、右の(メインの)列がリフレッシュされ、対応するデータが表示されます。上記のイメージの「リクエスト」タブには、リクエストされたURI (`/AffableBean/`)およびHTTPメソッド(`GET`)が表示され、さらにリクエストと一緒に送信された問合せ文字列はなかったことが示されています。
[start=4]
. 「セッション」タブを選択します。「このリクエストの結果、セッションが作成されました」という文が表示されています。これは、サーバーがレスポンスとして`JSESSIONID` Cookie`Set-Cookie`ヘッダーを送信したためです。また、新しいセッションIDが「セッション・プロパティ」の下に表示されています。後で示すように、セッションID`JSESSIONID` Cookieの値です。
image::images/session-tab.png[title="セッションの詳細がHTTPモニターの「セッション」タブの下に表示される"]
サイトの開始ページのリクエストからセッション・オブジェクトが作成された方法について疑問に思うかもしれません。結局、`ControllerServlet``/AffableBean/`の最初のリクエストを処理しておらず、このリクエストが`getSession()`の影響を受ける機会はどこにもありません。それとも、これが行われたのでしょうか。JSPページは、デプロイメント時にサーブレットにコンパイルされることを思い出してください。サーバーにプロジェクトをデプロイすれば、実際にIDEを使用して、サーバー上のJSPのコンパイルされたサーブレットを表示できます。
[start=5]
. 「プロジェクト」ウィンドウで`index.jsp`ファイルを右クリックし、「サーブレットを表示」を選択します。エディタで`index_jsp.java`ファイルが開きます。これは、`index.jsp`ページから自動的にコンパイルされたサーブレットです。
[start=6]
. このファイルで`getSession`を検索します。[Ctrl]-[F] (Macの場合は[⌘]-[F])を押し、検索バーで「`getSession`」と入力してから[Enter]を押します。
[Ctrl]-[F] (Macの場合は[⌘]-[F])は、「編集」>「検索」のキーボード・ショートカットです。
image::images/get-session.png[title="JSPページのコンパイルされたサーブレットに存在するgetSessionメソッド"]
実は、`getSession`メソッドはコールされます。これが起こる理由は、JSPページにはデフォルトで`pageContext.session`の暗黙オブジェクトが含まれるためです。この動作を非アクティブ化するには、JSPファイルの最初に次のディレクティブを追加します。
[source,java]
----
<%@page session="false" %>
----
このようにすると、コンパイルされたサーブレットの`getSession`メソッドが除去されます。
サーバー上のコンパイルされたサーブレットの場所を見つけるには、エディタの上にあるサーブレット名のタブ上にカーソルを移動します。ポップアップに、コンピュータ上のファイルへのパスが表示されます。
[start=7]
. ブラウザで、カテゴリを選択してからカートに項目を追加します。IDEに戻ります。以前に設定した`ControllerServlet`のブレークポイント(150行目)でデバッガは一時停止されます。すべてのブレークポイントは、セッション間で記憶されます。ブレークポイントを除去するには、エディタの左マージンにある「ブレークポイント」(image::images/breakpoint-badge.png[])バッジをクリックします。しかし、このプロジェクトにはすでに複数のブレークポイントが設定されているため、「ウィンドウ」>「デバッグ」>「ブレークポイント」でデバッガの「ブレークポイント」ウィンドウを開きます。
image::images/breakpoints-window.png[title="「ブレークポイント」ウィンドウでのプロジェクトのすべてのブレークポイントの表示"]
「ブレークポイント」ウィンドウから、IDEで開いているプロジェクトで設定されたすべてのブレークポイントを表示してアクションをコールできます。
[start=8]
. `header.jspf`に設定されたブレークポイントを右クリックして、「削除」を選択します。その後、`ControllerServlet`に設定されたブレークポイントを右クリックして、「無効」を選択します。(この課題の後半で再度有効にします。)
[start=9]
. 「続行」(image::images/continue-btn.png[])ボタンをクリックします。リクエストの実行が終了し、カートに項目が1つ追加されたカテゴリ・ページがブラウザに表示されます。
[start=10]
. HTTPモニターの左の列で`addToCart`リクエストを検索して選択すると、メインの列に詳細が表示されます。
昇順ソート(image::images/ascending-sort-btn.png[])ボタンをクリックすると、最新のレコードが最上部に表示されます。
「リクエスト」タブの下で、リクエストされたURI (`/AffableBean/addToCart`)、HTTPメソッド(`POST`)、およびリクエストされたパラメータ(`productId`および`submit`)を確認してください。
[.feature]
--
image:images/http-monitor-add-to-cart.png[role="left", link="images/http-monitor-add-to-cart.png"]
--
[start=11]
. Cookie」タブを選択します。ここでは、`JSESSIONID`という名前のCookieが存在し、クライアントからサーバーに送信されたことを確認できます。Cookieの値は、「セッション」タブの下に表示されたセッションIDと同じです。
image::images/cookies-tab.png[title="HTTPモニターの「Cookie」タブに表示されたCookie"]
同様に、「ヘッダー」タブをクリックするとCookieが表示されます。これは、「`Cookie`」がクライアントによって送信されたリクエスト・ヘッダーであるためです。
image::images/headers-tab.png[title="HTTPモニターの「Cookie」タブに表示されたCookie"]
リクエストおよびレスポンス・ヘッダーの詳細は、ウィキペディアのlink:http://en.wikipedia.org/wiki/List_of_HTTP_headers[+HTTPヘッダーの一覧+]を参照してください。
[start=12]
. 「セッション」タブを選択します。「このリクエストの前にセッションが存在しました」という文が表示されています。また、`cart`属性が「リクエスト後のセッション属性」の下に表示されています。`addToCart`リクエストが初めて処理されるときに`cart`オブジェクトがセッションにバインドされるため、これは理にかなっています。
image::images/session-tab-add-to-cart.png[title="HTTPモニターの「セッション」タブの下に表示されたセッション属性"]
以降のいくつかの手順では、「変数」ウィンドウでセッションIDおよび`JSESSIONID` Cookieを確認します。
[start=13]
. 以前に`ControllerServlet`に設定したブレークポイントを再度有効にします。[Alt]-[Shift]-[5] (Macの場合は[Ctrl]-[Shift]-[5])を押して「ブレークポイント」ウィンドウを開き、ブレークポイント・エントリの横にあるチェックボックスをクリックして、再度有効にします。
[start=14]
. ブラウザで、一覧表示された製品のうちの1つで「add to cart」ボタンをクリックします。
[start=15]
. IDEに切り替えると、デバッガが`ControllerServlet`に設定されたブレークポイントで一時停止されています。「ステップ・オーバー」(image::images/step-over-btn.png[])ボタンをクリックして、`session`変数をセッション・オブジェクトに割り当てます。
[start=16]
. 「変数」ウィンドウを開き([Alt]-[Shift]-[1]、Macの場合は[Ctrl]-[Shift]-[1])、「session」>「session」を展開します。セッションIDが「`id`」変数の値として表示されます。
[start=17]
. `JSESSIONID` Cookieを見つけるために、通常は`HttpServletRequest`link:http://java.sun.com/webservices/docs/1.6/api/javax/servlet/http/HttpServletRequest.html#getCookies%28%29[+`getCookies`+]メソッドをコールすることで、サーブレットからCookieにアクセスできることを思い出してください。したがってrequestオブジェクトを、「request」>「継承」>「request」>「request」>「継承」>「cookies」と展開します。これで`cookies` ArrayListが表示されます。リストを展開すると`JSESSIONID` Cookieがあり、この値がセッションIDになります。
[start=18]
. セッションの終了(image::images/finish-session-btn.png[])ボタンをクリックして、デバッグ・セッションを終了します。
[[url-rewrite]]
=== URL書換えによるセッションの維持
前述のように、サーブレット・エンジンはクライアント・ブラウザでCookieがサポートされているかどうかを検出し、サポートされていない場合は、セッションを維持する手段をURL書換えに切り替えます。これはすべて、クライアントにとって透過的に行われます。開発者にとっては、このプロセスは完全に透過的なわけではありません。
Cookieが無効になっている場合に必ずURL書換えができるようにアプリケーションを実装する必要があります。このために、アプリケーション内でサーブレットが返すURLのすべてに対して、レスポンスの`encodeURL`メソッドをコールします。このようにすると、Cookieが使用できない場合にはセッションIDURLに付加されるようになります。これを行わないと、URLを変更せずに返すことになります。
たとえば、ブラウザが次のような`AffableBean`3つ目のカテゴリ(bakery)のリクエストを送信します: `category?3`。サーバーは次のように、セッションIDURLに含めて応答します。
[source,java]
----
/AffableBean/category*;jsessionid=364b636d75d90a6e4d0085119990*?3
----
前述のように、_アプリケーションのサーブレットによって返されるすべてのURLをエンコードする必要があります_JSPページはサーブレットにコンパイルされることを思い出してください。JSPページでどのようにURLをエンコードするのでしょうか。JSTLlink:http://java.sun.com/products/jsp/jstl/1.1/docs/tlddocs/c/url.html[+`<c:url>`+]タグがこの機能を果たします。以下の課題で、問題と解決法を示します。
1. ブラウザのCookieを一時的に無効にします。Firefoxを使用している場合、「ツール」>「オプション」(Macの場合は「Firefox」>「プリファレンス」)を選択します。表示されたウィンドウで「プライバシー」タブを選択してから、表示されたドロップダウンで「記憶させる履歴を詳細設定する」を選択します。「サイトから送られてきたCookieを保存する」オプションを選択解除します。
image::images/firefox.png[title="ブラウザでの一時的なCookieの無効化"]
[start=2]
. `AffableBean`プロジェクトを実行します。開始ページが表示されたら、カテゴリをクリックしてからカートに項目を追加してみてください。現状ではアプリケーションがまったく機能しません。
image::images/compromised.png[title="クライアントがCookieを受け入れない場合、アプリケーションは機能しない"]
以前と同様に、サーバーはセッションを生成して、そのセッションにオブジェクトをバインドします。このようにして、選択されたカテゴリおよび製品をカテゴリ・ページに表示できます。しかし、サーバーは`JSESSIONID` Cookieの設定に失敗しました。したがって、クライアントが2つ目のリクエストを作成したとき(ユーザーが「add to cart」をクリックしたとき)、サーバーにはリクエストが属しているセッションを識別する方法がありません。このため、以前にセッションで設定された`selectedCategory``categoryProducts`などの属性をいずれも見つけることができません。このような理由により、レスポンスのレンダリングにはこれらの属性によって指定される情報が欠如しています。
[start=3]
. エディタでプロジェクトの`category.jsp`ページを開きます。「add to cart」ボタンを実装する行(58行目)を見つけます。`<form>`要素の`action`属性によって、サーバーに送信されるリクエストが決まります。
[source,java]
----
<form action="addToCart" method="post">
----
[start=4]
. リクエストを変更して、`<c:url>`タグを通して渡されるようにします。
[source,java]
----
<form action="*<c:url value='addToCart'/>*" method="post">
----
[start=5]
. [Ctrl]-[S] (Macの場合は[⌘]-[S])を押して、ファイルへの変更を保存します。IDEは、デフォルトで有効な「保存時にデプロイ」機能が備わっていることを思い出してください。これによって、保存された変更はすべて自動的にサーバーにデプロイされます。
[start=6]
. ブラウザで異なるカテゴリを選択して、新しく変更されたカテゴリ・ページをアプリケーションにレンダリングさせます。
[start=7]
. このページのソース・コードを調べます。Firefoxでは[Ctrl]-[U] (Macの場合は[⌘]-[U])を押します。各製品の「add to cart」ボタンに、URLに付加されたセッションIDが一緒に表示されています。
[source,java]
----
<form action="addToCart*;jsessionid=4188657e21d72f364e0782136dde*" method="post">
----
[start=8]
. いずれかの項目で「add to cart」ボタンをクリックします。サーバーが、リクエストが属しているセッションを判定して、適切にレスポンスをレンダリングできるようになっていることを確認できます。
[start=9]
. 次に進む前に、ブラウザでCookieを再度有効にしてください。
前述のように、アプリケーション内でユーザーがクリックできるすべてのリンクは、そのレスポンスがなんらかの形式のセッション関連のデータを必要とする場合、適切にエンコードされる必要があります。上記の例ほど実装が単純ではない場合もあります。たとえば、`cart.jsp`で使用されている「clear cart」ウィジェットは、現時点ではリンクがクリックされると`clear`パラメータを`true`に設定します。
[source,xml]
----
<%-- clear cart widget --%>
<c:if test="${!empty cart &amp;&amp; cart.numberOfItems != 0}">
<a href="viewCart*?clear=true*" class="bubble hMargin">clear cart</a>
</c:if>
----
`<c:url>`タグは、次の方法でURLに適用できます。
[source,xml]
----
<%-- clear cart widget --%>
<c:if test="${!empty cart &amp;&amp; cart.numberOfItems != 0}">
*<c:url var="url" value="viewCart">
<c:param name="clear" value="true"/>
</c:url>*
<a href="*${url}*" class="bubble hMargin">clear cart</a>
</c:if>
----
`clear=true`パラメータは、`<c:url>`タグ間に`<c:param>`タグを追加することで設定されます。「`url`」という名前の変数が<c:url> `var`属性を使用して設定された後、`var``${url}`式を使用してHTMLアンカー・タグでアクセスされます。
link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot6.zip[+スナップショット6+]をダウンロードして調べれば、プロジェクトのすべてのリンクがどのようにエンコードされているかを確認できます。
URL書換えは、トラック・メソッドとしてCookieが使用できない場合にのみ使用する必要があります。セッションIDがブラウザのアドレス・バーのみでなく、ログ、ブックマーク、リファラ・ヘッダーおよびキャッシュされたHTMLに表示されるため、通常はURL書換えは次善手段と見なされています。また、受信リクエストごとにURLからセッションIDを抽出して、既存のセッションと組み合せるためのサーバーの追加手順が必要なため、サーバー側リソースも多く必要になります。
[[time-out]]
== セッション・タイム・アウトの処理
* <<time-interval,セッションの時間間隔の設定>>
* <<programmatically,プログラムによるセッション・タイム・アウトの処理>>
[[time-interval]]
=== セッションの時間間隔の設定
サーバーがセッションを維持する時間間隔の最大値について考慮する必要があります。Webサイトのトラフィックが多くなると、多数のセッションによってサーバーのメモリー・キャパシティが使い果たされる可能性があります。このため、使用されていないセッションを除去できるように、時間間隔を短くできます。一方で、セッションが短すぎると使い勝手が悪くなり、Webサイトの背後にあるビジネスに悪影響を与える可能性があります。これは避ける必要があります。`AffableBean`アプリケーションの例で考えると、ユーザーがショッピング・カートに多くの項目を入れてからチェックアウトに進むとします。その後、クレジット・カードの詳細を入力する必要があることがわかり、財布を探しにいきます。クレジット・カードを持ってコンピュータに戻ってから、チェックアウト・フォームに入力して「submit」をクリックします。しかしこの間に、このユーザーのセッションはサーバーで有効期限切れになっていました。ショッピング・カートは空になり、ユーザーはホーム・ページにリダイレクトされます。このユーザーは、再度時間をかけて同じプロセスを実行するでしょうか。
次の手順では、`AffableBean`プロジェクトのセッション・タイム・アウトの間隔を10分に設定する方法を示します。言うまでもなく実際の間隔は最終的に、サーバー・リソース、業務におけるアプリケーションの目標、およびWebサイトの需要によって決まります。
1. エディタでアプリケーションのデプロイメント・ディスクリプタを開きます。[Alt]-[Shift]-[O] (Macの場合は[Ctrl]-[Shift]-[O])を押して、IDEの「ファイルに移動」ダイアログを使用します。「`web`」と入力してから「OK」をクリックします。
image::images/go-to-file.png[title="「ファイルに移動」ダイアログを使用した、プロジェクト・ファイルへの速やかな移動"]
エディタの「XML」ビューに`web.xml`ファイルが表示されます。NetBeans`web.xml`ファイルに提供しているテンプレートでは、デフォルトとして30分が設定されています。
[source,xml]
----
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
----
[start=2]
. 「一般」タブをクリックして、「セッション・タイム・アウト」フィールドに「`10`」と入力します。
image::images/session-timeout.png[title="web.xmlの「一般」タブでの、アプリケーションのセッション・タイムアウトの指定"]
[start=3]
. ファイルを保存します([Ctrl]-[S]、Macの場合は[⌘]-[S])。
XML」ビューに戻ると、`<session-timeout>`要素が更新されていることを確認できます。
[source,xml]
----
<session-config>
<session-timeout>10</session-timeout>
</session-config>
----
*注意: *かわりに、`<session-timeout>`要素をすべて除去し、GlassFish固有のデプロイメント・ディスクリプタ(`sun-web.xml`)で`session-properties`要素を編集することもできます。これを行うと、サーバーのWebモジュールにあるすべてのアプリケーションのグローバル・タイム・アウトが設定されます。詳細は、link:http://docs.sun.com/app/docs/doc/821-1752/beaha[+Oracle GlassFish Server 3.0.1アプリケーション開発ガイド: セッションの作成および管理+]を参照してください。
[[programmatically]]
=== プログラムによるセッション・タイム・アウトの処理
アプリケーションがセッションに依存している場合、タイム・アウトしたセッションや識別できないセッションに対するリクエストを受け取ったときにも正常に処理できるようにする必要があります。`AffableBean`アプリケーションでこれを実現するには、`ControllerServlet`に送信されたリクエストをインターセプトする単純なフィルタを作成します。このフィルタはセッションが存在するかどうかをチェックして、存在しなければリクエストをサイトの開始ページに転送します。
1. まず、ユーザーがサイトにアクセスしている途中でセッションがタイム・アウトする場合に起こる問題を調べます。一時的に、セッション・タイム・アウトの間隔を1分にリセットします。Webデプロイメント・ディスクリプタ(`web.xml`)を開き、`<session-timeout>`タグの間に「`1`」を入力します。
[source,xml]
----
<session-config>
<session-timeout>*1*</session-timeout>
</session-config>
----
[start=2]
. `AffableBean`プロジェクトを実行します。ブラウザでカテゴリ・ページをクリックして、項目をいくつかカートに追加してから「view cart」をクリックします。
image::images/cart-page-session-intact.png[title="カート・ページにはセッション・オブジェクトに応じてショッピング・カートの項目が表示される"]
[start=3]
. 少なくとも1分間待ちます。
[start=4]
. カート・ページに表示された項目のうち、1つの項目の数量を更新します。(1から99までのいずれかの数字を入力できます。)「update」をクリックします。サーバーからHTTPステータス500のメッセージが返されます。
image::images/glassfish-error-report.png[title="有効期限切れセッションに対するリクエストを受信するとNullPointerExceptionが発生する"]
[start=5]
. IDEGlassFishサーバー・ログを調べます。「出力」ウィンドウ([Ctrl]-[4]、Macの場合は[⌘]-[4])を開き、「GlassFish Server」タブを選択します。ログの最下部までスクロールして、エラーのスタック・トレースを調べます。
[.feature]
--
image:images/gf-server-output.png[role="left", link="images/gf-server-output.png"]
--
サーバー・ログに、`ControllerServlet`184行目で`NullPointerException`が発生したことが示されています。「出力」ウィンドウには、例外が発生した行へのリンクが表示されます。
[start=6]
. リンクをクリックします。`ControllerServlet`184行目に直接移動します。エディタの左マージンにあるエラー・バッジの上にカーソルを置くと、例外を説明するツールチップが表示されます。
image::images/nullpointer-exception.png[title="エラー・バッジとツールチップによる問題の場所および原因の提示"]
リクエストを受け取る前にセッションがすでに有効期限切れになっていたため、サーブレット・エンジンは、対応するセッションにリクエストを関連付けることができませんでした。このため、`cart`オブジェクトを見つけられませんでした(151行目)。最終的に184行目で、`null`と等しい変数のメソッドをエンジンがコールしようとしたときに例外が発生しました。
これで問題を特定できたので、フィルタを実装して修正しましょう。
[start=7]
. IDEのメイン・ツールバーにある「新規ファイル」(image::images/new-file-btn.png[])ボタンをクリックします。または、[Ctrl]-[N] (Macの場合は[⌘]-[N])を押します。
[start=8]
. 「*Web*」カテゴリから「*フィルタ*」を選択し、「次」をクリックします。
[start=9]
. フィルタに「`SessionTimeoutFilter`」という名前を付けます。「パッケージ」フィールドに「`filter`」と入力して、フィルタ・クラスが作成時に新しいパッケージに配置されるようにします。
[start=10]
. 「次」をクリックします。デフォルトの設定を受け入れ、「終了」をクリックします。`SessionTimeoutFilter`のテンプレートが生成され、エディタで開きます。
*注意:* NetBeans 6.9の時点では、ウィザードを使用してWebデプロイメント・ディスクリプタに登録されていないサーブレットへのマッピングを設定できません。(`ControllerServlet``@WebServlet`注釈を使用して登録されています。)このため、生成されたコードを次の手順で変更します。
[start=11]
. `@WebFilter`注釈署名を次のように変更します。
[source,java]
----
@WebFilter(*servletNames = {"Controller"}*)
public class SessionTimeoutFilter implements Filter {
----
これによって、`ControllerServlet`が処理するすべてのリクエストをインターセプトするようにフィルタが設定されます。(または、`urlPatterns`属性を保持して、`ControllerServlet`が処理するすべてのパターンをリストすることもできます。)
サーブレットの`@WebServlet`注釈署名に指定されているように、「`Controller`」は`ControllerServlet`の名前です。また、フィルタ・クラスの名前がデフォルトで使用されているため、`filterName`属性は除去しています。
IDEのフィルタ・テンプレート自体に、調べる価値のある有用なコードが多く含まれています。ただし、そのほとんどはここでの目的には不要なものです。どのフィルタ・クラスも、次の3つのメソッドを定義する`Filter`インタフェースを実装する必要があります。
* *`init`:* フィルタが初期化されてから使用が開始されるまでの間に、任意のアクションを実行します。
* *`destroy`:* 使用を停止してフィルタを除去します。このメソッドは、任意のクリーン・アップ操作を実行するためにも使用できます。
* *`doFilter`:* フィルタがインターセプトするリクエストごとの操作の実行に使用されます。
`Filter`インタフェースに関するドキュメントを参照するには、「Javadoc索引検索」を使用します。[Shift]-[F1] (Macの場合は[fn]-[Shift]-[F1])を押してから、検索フィールドに「`Filter`」と入力して[Enter]を押します。「Interface in javax.servlet」エントリを選択します。索引検索ツールの下部のペインに、Javadocドキュメントが表示されます。
[start=12]
. `SessionTimeoutFilter`の本文を、次の内容に置き換えます。
[source,java]
----
@WebFilter(servletNames = {"Controller"})
public class SessionTimeoutFilter implements Filter {
*public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession(false);
// if session doesn't exist, forward user to welcome page
if (session == null) {
try {
req.getRequestDispatcher("/index.jsp").forward(request, response);
} catch (Exception ex) {
ex.printStackTrace();
}
return;
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}*
}
----
[start=13]
. [Ctrl]-[Shift]-[I] (Macの場合は[⌘]-[Shift]-[I])を押してインポート文を修正します。(`HttpServletRequest`および`HttpSession`のためのインポートの追加が必要。)また、エディタのヒントを使用して`init``destroy`および`doFilter`メソッドに`@Override`注釈を追加します。
以降の手順では、プロジェクトでデバッガを実行して`doFilter`メソッドをステップ実行し、リクエストが既存のセッションにバインドされているかどうかをこのメソッドが判定する方法を確認します。
[start=14]
. 「ブレークポイント」ウィンドウを開き([Alt]-[Shift]-[5]、Macの場合は[Ctrl]-[Shift]-[5])、既存のブレークポイントが設定されていないことを確認します。ブレークポイントを削除するには、ブレークポイントを右クリックして「削除」を選択します。(前述の課題である<<http-monitor,HTTPモニターによるクライアントとサーバー間の通信の確認>>を完了した場合は、`ControllerServlet`に未処理のブレークポイントが設定されている可能性があります。)
[start=15]
. デバッガを実行します。IDEのメイン・ツールバーにある「プロジェクトをデバッグ」(image::images/debug-project-btn.png[])ボタンをクリックします。
[start=16]
. ブラウザに開始ページが表示されたら、カテゴリを選択してからショッピング・カートにいくつか項目を追加します。
[start=17]
. `SessionTimeoutFilter``doFilter`メソッドで、セッションへのアクセスを試行する行(32行目)にブレークポイントを設定します。
image::images/filter-breakpoint.png[title="getSessionメソッドへのブレークポイントの設定"]
[start=18]
. ブラウザで「view cart」ボタンをクリックします。IDEに切り替えると、デバッガがブレークポイントで一時停止されていることがわかります。
その時点でセッション・オブジェクトが存在しない場合、`getSession()`は新しいセッション・オブジェクトを作成することを思い出してください。ここでは、オブジェクトが見つからなくても新規作成を行わない`getSession(false)`を使用します。つまり、セッションが存在しない場合、メソッドは`null`を返します。
[start=19]
. 「ステップ・オーバー」(image::images/step-over-btn.png[])ボタンをクリックしてから、`session`変数の上にカーソルを移動します。前回のリクエストが送信されてから1分が経過していなければ、変数が`StandardSessionFacade`に割り当てられていることを確認できます。これは、リクエストのセッション・オブジェクトを表します。
image::images/session-exists.png[title="変数へのカーソルの移動によるその現在値の判定"]
[start=20]
. リクエストが処理されるまで、メソッドをステップ実行し続けます。`session``null`と等しくないため、`if`文をスキップすると、`chain.doFilter`はリクエストを`ControllerServlet`に転送します(44行目)。
[start=21]
. ブラウザで、1分間が過ぎたことを確認してから、カートの製品項目のうちの1つの数量を更新します。この手順は、この課題の最初の方で実行してステータス500のメッセージが返されたときと同じ手順です。ここでは、`ControllerServlet`に送信されたリクエストをフィルタがインターセプトするようになったので、セッション・タイム・アウトが起きるとどうなるかを確認しましょう。
[start=22]
. update」をクリックしてからIDEに切り替えると、フィルタに設定されたブレークポイントでデバッガが再度一時停止されています。
[start=23]
. `req.getSession(false)`の式を強調表示してから、この上にカーソルを移動します。ここでは、セッションがすでに有効期限切れになっているため、式が`null`と等しいことが確認できます。
image::images/session-null.png[title="強調表示した式へのカーソルの移動によるその現在値の判定"]
[start=24]
. 続けてコードをステップ実行します。ここでは、`session`変数が`null`と等しくなっているため、35行目の`if`文が処理され、リクエストが`/index.jsp`に転送されます。デバッガが実行を終了すると、ブラウザにサイトの開始ページが表示されます。
[start=25]
. セッションの終了(image::images/finish-session-btn.png[])ボタンをクリックして、デバッグ・セッションを終了します。
[start=26]
. プロジェクトの`web.xml`ファイルを開き、セッション・タイム・アウトの間隔を10分に戻します。
[source,xml]
----
<session-config>
<session-timeout>*10*</session-timeout>
</session-config>
----
[start=27]
. ファイルを保存([Ctrl]-[S]、Macの場合は[⌘]-[S])します。
link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot6.zip[+スナップショット6+]は、このチュートリアル・ユニットのプロジェクトの完成版を示しています。最後に、セッション管理に関する1つのトピックについて説明します。セッション・オブジェクトで`invalidate`メソッドをコールすることで、セッションを明示的に終了させることができます。セッションが不要になったら、サーバーが使用するメモリーを節約するために、そのセッションは除去するようにしてください。次のユニットであるlink:transaction.html[+ビジネス・ロジックの取引の統合+]を完了すると、顧客の注文を正常に処理したときに`ControllerServlet`が`invalidate`メソッドを使用してどのようにユーザーの`cart`オブジェクトを破棄し、セッションを終了するかがわかります。
[source,java]
----
// if order processed successfully send user to confirmation page
if (orderId != 0) {
// dissociate shopping cart from session
cart = null;
// end session
session.invalidate();
...
}
----
これは、link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot8.zip[+プロジェクト・スナップショット8+] (およびそれ以降のスナップショット)に示されています。
link:/about/contact_form.html?to=3&subject=Feedback: NetBeans E-commerce Tutorial - Managing Sessions[+ご意見をお寄せください+]
[[seeAlso]]
== 関連項目
=== NetBeansリソース
* link:../../../../features/java/debugger.html[+NetBeans IDEの機能: デバッガ+]
* link:../../java/debug-multithreaded.html[+マルチスレッド・アプリケーションのデバッグ+]
* link:../../java/debug-multithreaded-screencast.html[+NetBeans IDEを使用したマルチスレッド・デバッグのビデオ+]
* link:../../java/debug-evaluator-screencast.html[+NetBeansデバッガのコード・スニペット評価の使用のビデオ+]
* link:../../screencasts.html[+NetBeans IDE 6.xのビデオ・チュートリアルとデモ+]
* link:https://netbeans.org/projects/www/downloads/download/shortcuts.pdf[+キーボード・ショートカットおよびコード・テンプレートのカード+]
* link:../javaee-gettingstarted.html[+Java EE 6アプリケーションの開始+]
* link:../javaee-intro.html[+Java EEテクノロジ入門+]
* link:../../../trails/java-ee.html[+Java EEおよびJava Webの学習+]
=== GlassFishリソース
* link:http://wiki.glassfish.java.net/Wiki.jsp?page=Screencasts[+GlassFishスクリーンキャスト+]
* link:https://glassfish.dev.java.net/docs/index.html[+GlassFish v3ドキュメント+]
* link:http://www.sun.com/offers/details/GlassFish_Tomcat.html[+Tomcatユーザー向けGlassFishの学習+]
* link:http://docs.sun.com/app/docs/doc/821-1751[+Oracle GlassFish Server 3.0.1管理ガイド+]
* link:http://docs.sun.com/app/docs/doc/821-1750[+Oracle GlassFish Server 3.0.1アプリケーション・デプロイメント・ガイド+]
* link:http://docs.sun.com/app/docs/doc/821-1752[+Oracle GlassFish Server 3.0.1アプリケーション開発ガイド+]
=== 技術記事およびその他のリソース
* link:http://java.sun.com/javaee/reference/code/[+Java EEコード・サンプルおよびアプリケーション+]
* link:http://java.sun.com/j2se/javadoc/[+Javadocツール+] (製品ホーム・ページ)
* link:http://java.sun.com/j2se/javadoc/writingdoccomments/index.html[+Javadocツールのドキュメント・コメントを書く方法+]
* link:http://java.sun.com/products/servlet/Filters.html[+フィルタの基本+]
* link:http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html[+コアJ2EEパターン - フィルタのインターセプト+]
* link:http://courses.coreservlets.com/Course-Materials/csajsp2.html[+サーブレット、JSPおよびJDBCの初級・中級レベル・チュートリアル+]
* link:http://courses.coreservlets.com/Course-Materials/msajsp.html[+サーブレットおよびJSPの上級チュートリアル+]
* link:http://courses.coreservlets.com/Course-Materials/java5.html[+Java 5およびJava 6チュートリアル+]
* link:http://www.ibm.com/developerworks/java/library/j-jstl0211.html[+JSTL入門、パート1: 式言語+]
* link:http://www.ibm.com/developerworks/java/library/j-jstl0318/index.html[+JSTL入門、パート2: coreライブラリについて+]