■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2008年02月03日

    楽しいJava講座 - 初心者から達人へのパスポート
                  vol.090

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.Tomcatのアプリケーション開発
========================================================

では、前回のcart.jspの説明をしておきます。
--------------------------------------------------------
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="Shift_JIS" %>

<HTML>
<HEAD>
<TITLE>ショッピング・カート</TITLE>
</HEAD>
<BODY bgcolor="#77ffff" text="#fa5a00">
<H1>ショッピング・カートの中のリスト</H1>
<FORM action="orderprocess" method="POST">
<TABLE border="1" width="100%">
  <TBODY>
    <TR>
      <TH>商品番号</TH>
      <TH>商品名</TH>
      <TH>価格</TH>
      <TH>カテゴリー</TH>
      <TH>イメージ</TH>
      <TH>購入個数</TH>
    </TR>

<%@ page import="java.util.Vector" %>
<%@ page import="jp.co.flsi.lecture.webapp.entity.*" %>
<jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Order" id="ORDER" scope="request" />
<%    OrderItem anOrderItem;
      StringBuffer sb = request.getRequestURL();
      for (int i = ORDER.getOrderItems().size() - 1; i >= 0 ; i--) {
         anOrderItem = ORDER.getOrderItems().elementAt(i); %>
         <TR>
            <TD><%= anOrderItem.getNum() %></TD>
            <TD><%= anOrderItem.getName() %></TD>
            <TD><%= anOrderItem.getPrice() %>円</TD>
            <TD><%= anOrderItem.getCategory() %></TD>
            <TD><IMG src="<%= sb.substring(0, sb.lastIndexOf("/")) %><%= anOrderItem.getImage() %>"></TD>
            <TD><INPUT TYPE="text" NAME="<%= anOrderItem.getNum() + "_quantity" %>"
                       VALUE="<%= anOrderItem.getOrderQuantity() %>" SIZE=1></TD>
          </TR>
<%    } %>

  </TBODY>
</TABLE>
<BR>
<BR>
<INPUT TYPE="submit" NAME="putIntoOrder" VALUE="選択した商品を購入する">
</FORM>
</BODY>
</HTML>
--------------------------------------------------------

まず最初に

<FORM action="orderprocess" method="POST">

の行は、いつものパターンで、省略されたURLを指定しています。
このように指定しておけば、実際には、

http://IPアドレス:8080/JStudy2/servlet/orderprocess

という形式のURLでサーブレットを呼び出すことになります。
これは購入の手続きをするためのサーブレットを呼び出すURLなので、
orderprocessという名前にしました。
(インターネット・ショッピングは、通信販売の一種なので、購入と
いう言葉よりも注文という言葉を使ったほうが適切ですが、リアルな
お店でのアナロジーから購入という言葉も使います。というわけで、
当メールマガジンでは、どちらの言葉も区別せずに使っています。)

なお、orderprocessに対応するサーブレットはあとで作ります。


このcart.jspは前回のitemList.jspの内容が理解できていれば、
ほとんど理解できると思いますが、このcart.jspがitemList.jsp
と大きく異なるところは、表の中の6番目の列が「選択」から
「購入個数」に変わっているところです。
itemList.jspではこの列にはチェック・ボックスを入れていました
が、cart.jspでは

<INPUT TYPE="text" NAME="<%= anOrderItem.getNum() + "_quantity" %>"
       VALUE="<%= anOrderItem.getOrderQuantity() %>" SIZE=1>

というコードによって、テキスト・フィールドにしています。
このパラメータ名はitemList.jspのときと同じく商品番号
(ただしItemとOrderItemというクラスの違いはありますが)
になっていますが、このテキスト・フィールドの初期値には
<%= anOrderItem.getOrderQuantity() %>
というコードによって、OrderItemのインスタンスから取り出し
た注文数が設定されます。

注文数のデフォルトは1にしていましたから、注文数の変更
を行っていない今の段階ではここには1が表示されることに
なります。

注文数をテキスト・フィールドで表示するようにしたのは、
ユーザー(注文者)がこの値を書き換えられるようにするため
です。
つまり、注文したい個数が1以外の場合は、ユーザーがこのテキ
スト・フィールドの値を書き換えることによって個数を変更で
きるようにします。(あとのサーブレットでそのようにプログ
ラミングします。)


次に、

for (int i = ORDER.getOrderItems().size() - 1; i >= 0 ; i--) {

の行を見てください。
このfor文では、ORDER.getOrderItems()によって取り出されるVectorの
うち、最後の要素([ORDER.getOrderItems().size() - 1]番目の要素)
から始まってi--によってiの値を1ずつ減らしながら、最初の要素
(0番目の要素)まで順番に処理をしています。
これは、Vectorの中を逆順に処理する方法を示しています。

HttpServletRequestのgetParameterNames()メソッドでパラメー
ターの名前を取り出すと、itemList.jspのWebページのフォームの
中に並んだ商品の順番と逆になってしまうので、このように逆順に
処理をしています。
ただし、この順番にはあまり意味はありません。
これは逆順に処理するコーディングのサンプルを提示しただけ
のことであり、商品のリストはこの順番にしなければならない
ということではありません。
本来なら、メーカーやカテゴリーごとに整理し直した上で商品名の
順番に並べ替えるとか、何らかの意味のある順番に並べ替えて見や
すく表示するような機能を加えたほうがいいのでしょうが、当メール
マガジンではアプリケーションを簡潔にするために、装飾的な細かい
機能は省略します。



さて、このJSPによって表示されるWebページは、ショッピング・カート
の中にはいっている商品を表示して、それらを購入(注文)するか
どうかを指示できる(「選択した商品を購入する」というボタンが
用意されている)ようにするためのものですが、実際に注文するに
あたっては、顧客(注文者)の住所・氏名などの情報を入力しておく
必要があります。(インターネット・ショッピングはあくまで通信販売
の一種なので、顧客の住所・氏名がわからなければ販売はできません。)

そこで、「選択した商品を購入する」ボタンを押したあとで顧客情報
(住所・氏名など)の入力のためのWebページを表示し入力を要求する
ようにしてもいいのですが、注文のたびに毎回住所・氏名を入力する
のは面倒です。
というわけで、通常は一度ユーザー登録という操作を行っておけば、
以後はユーザーIDとパスワードの入力(ログ・イン操作)だけで済み、
住所・氏名の入力は不要になるようにしているのが普通です。


というわけで、当Webアプリケーションの「選択した商品を購入する」
ボタンを押したあとのWebページではユーザーIDとパスワードの入力を
要求し、まだユーザー登録していない顧客に対してはユーザー登録を
促すようにします。
なお、アプリケーションを簡潔にするために、顧客番号をユーザーID
と兼用することにします。



ところで、前回OrderItemServletを作成したときにItemDbManagerを
使ってITEMテーブルから商品のデータを読み取っていたことを不思議
に思いませんでしたか?
ITEMテーブルにはいっている商品のデータは既にItemServletで読み取っ
ていたはずで、そのデータをitemList.jspでWebページに表示し、その
Webページで選択した商品を次のショッピング・カートのWebページで
再度表示するのだから、ItemServletで読み取っていたデータをショッ
ピング・カートのWebページでも再度使えばいいのではないか?
と思うでしょう。

しかし、実はWebサーバーは基本的にはクライアント(Webブラウザー)
から要求されたページをクライアントに返すだけの一過性の作業しか
行わない仕組みになっており、たとえユーザーにとっては以前の要求
と次の要求に連続性あるいは関連性があったとしても、Webサーバーは
そんなことは知らず、同じクライアントから以前どのような要求があっ
たかなどはまったく記憶していません。

サーブレットでも、今まで行ったようなプログラミングのやり方だと
通常のWebサーバーと同じく、以前の要求と現在処理している要求とは
連続性や関連性がまったくない状態になってしまいます。したがって、
OrderItemServletが呼び出された段階では以前どんな商品情報がデータ
ベースから読み出されたかなどは記憶していないため、再度データべー
スから読み込みを行うようにしているのです。


しかしながら、実はサーブレット(JSPも含む)は通常のWebサーバー
とは異なり、「セッション」を管理する機能が用意されているため、
以前の要求時に処理した情報を記憶させて使うことができます。

「セッション(session)」というのは、連続した会話(あるいは通信)
の一区切りを表す言葉で、たとえば電話を例にあげると、AさんがBさん
に電話をかけておしゃべりし、おしゃべりが終わって電話を切るまで
が一つのセッションになります。
インターネット・ショッピングの場合は顧客とお店(正確には顧客が
使っているWebブラウザーとお店が使っているサーバー)が通信をする
わけですが、顧客がショッピングを始めてから購入(注文)手続きを
完了させるまでを一つのセッションと見なすことができます。

そして、サーブレットのセッションの機能を利用すると、顧客がショッ
ピングを始めてから、注文を完了するまでの間、ショッピング・カート
に入っている商品の情報など必要な情報を記憶しておくことができます。

そうするとその後は毎回データベースからデータを取り直す必要はなく
なります。



サーブレットではセッションを表現するオブジェクトをHttpServletRequest
のgetSession()というメソッドによって取り出し、もしくは生成すること
ができます。

getSession()メソッドを呼び出すと、現在のセッションを表現するセッション・
オブジェクトが戻り値として返され、セッション・オブジェクトが存在しない
場合はセッション・オブジェクトを新規に生成してそれを戻り値として返し
てくれます。ただし、これは引数なしのgetSession()メソッドを使った
場合です。

このgetSession()メソッドには、引数なしのメソッドの他に、boolean型の
引数を持つメソッドもあります。
セッション・オブジェクトが存在しないときでもセッション・オブジェクト
を新規に生成して欲しくなければboolean型の引数を持つgetSession()メソッ
ドを使ってgetSession(false)という形でメソッド呼び出しを行います。
この場合、セッション・オブジェクトが存在しないときには戻り値として
nullが返されます。

なお、getSession(true)という形でメソッド呼び出しを行った場合は、
引数なしのgetSession()メソッドを呼び出したときと同じになります。

これらの戻り値のセッション・オブジェクトはHttpSession型であり、
HttpSessionはインターフェースです。
(HttpServletRequest自体もインターフェースです。これらのメソッド
はWebコンテナー(サーブレット・コンテナー)を作ったメーカーが
実装しています。)

そして、セッションが終了したことを示すためには、セッション・
オブジェクトを無効にします。
そのためには、セッション・オブジェクト(HttpSession)のinvalidate()
メソッドを呼び出します(invalidateには無効化するという意味があります)。
あるいは、ある決まった時間使われないと自動的に無効になるように設定して
おくこともできます。

セッション・オブジェクトに関連して、もう一つ頭に入れておかなければならない
ことがあります。
それは、Webブラウザーを操作するユーザーは、必ずしもこれまでにプログラミ
ングしてきたような順番にWebページを操作してくれるわけではない、という
ことです。

たとえば、商品をショッピング・カートに入れたあと、Webブラウザーの
「戻る」ボタンをクリックして商品検索のページまで戻ってから、
他の商品を追加購入しようとするかもしれません。
あるいは、商品検索のページをあらかじめお気に入り(ブックマーク)に
登録しておき、商品をショッピング・カートに入れたあとで、お気に入り
から商品検索のページを開き直して他の商品を追加購入しようとするかも
しれません。

このような場合、これまでのプログラミングのやり方では、既にショッピング・
カートに入れていた商品の情報が消滅してしまい、不都合が生じてしまいます。

しかし、ショッピング・カートの内容をセッション・オブジェクトを使っ
て保存しておけば、セッションを終了させない限り、ショッピング・
カートの中の商品の情報が消滅することはありません。

また、場合によっては、ショッピング・カートのページから注文処理の
ページに移って注文処理を完了させたのに、間違えて「戻る」ボタンを
クリックしてショッピング・カートのページに戻ってしまい、再度
注文処理を繰り返して同じ注文を重複して2回繰り返してしまうという
ことも起こりえますが、そこらへんもセッション・オブジェクトに管理
させ、注文が完了したらショッピング・カートの中が空であることを
記憶させるようにしておけば、間違えてショッピング・カートのページ
に戻っても、中が空なので注文はできないということになります。

というわけで、こういったWebアプリケーションでは、セッション・オブ
ジェクトは欠かせないものと言えます。


ここまでお話すると、インターネット・ショッピングのWebアプリケーション
は簡単そうに見えて、実際はずいぶん複雑なものであることがわかってくる
と思います。



では、これから、セッション・オブジェクトを使ってアプリケーション
を編集していきましょう。

まずは、OrderItemServletを下記のように書き換えてみてください。
--------------------------------------------------------
package jp.co.flsi.lecture.webapp.servlet;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jp.co.flsi.lecture.webapp.db.ItemDbManager;
import jp.co.flsi.lecture.webapp.entity.Order;
import jp.co.flsi.lecture.webapp.entity.OrderItem;
import jp.co.flsi.lecture.webapp.entity.Item;

public class OrderItemServlet extends HttpServlet {
   public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
      req.setCharacterEncoding("Shift_JIS");
      HttpSession session = req.getSession();
      Order anOrder = (Order)session.getAttribute("ORDER");
      if (anOrder == null) anOrder = new Order();
      ItemDbManager itemDb = new ItemDbManager();
      itemDb.selectAllData();
      Enumeration paramNames = req.getParameterNames();
      while(paramNames.hasMoreElements()) {
         String aParamName = (String)paramNames.nextElement();
         if (!aParamName.equals("putIntoCart")) {
            OrderItem anOrderItem = new OrderItem();
            for (Item anItem : itemDb.getItemList()) {
               if(aParamName.equals(anItem.getNum())) {
                  anOrderItem.setNum(anItem.getNum());
                  anOrderItem.setName(anItem.getName());
                  anOrderItem.setPrice(anItem.getPrice());
                  anOrderItem.setImage(anItem.getImage());
                  anOrderItem.setCategory(anItem.getCategory());
               }
            }
            anOrder.addOrderItem(anOrderItem);
         }
      }
      session.setAttribute("ORDER",anOrder);
      getServletContext().getRequestDispatcher("/cart.jsp").forward(req,res);
   }

}
--------------------------------------------------------

続いて、cart.jspを次のように下記のように書き換えてみてください。
--------------------------------------------------------
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="Shift_JIS" %>

<HTML>
<HEAD>
<TITLE>ショッピング・カート</TITLE>
</HEAD>
<BODY bgcolor="#77ffff" text="#fa5a00">
<H1>ショッピング・カートの中のリスト</H1>
<FORM action="orderprocess" method="POST">
<TABLE border="1" width="100%">
  <TBODY>
    <TR>
      <TH>商品番号</TH>
      <TH>商品名</TH>
      <TH>価格</TH>
      <TH>カテゴリー</TH>
      <TH>イメージ</TH>
      <TH>購入個数</TH>
    </TR>

<%@ page import="java.util.Vector" %>
<%@ page import="jp.co.flsi.lecture.webapp.entity.*" %>

<jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Order" id="ORDER" scope="session" />
<%      OrderItem anOrderItem;
      StringBuffer sb = request.getRequestURL();
      for (int i = ORDER.getOrderItems().size() - 1; i >= 0 ; i--) {
         anOrderItem = ORDER.getOrderItems().elementAt(i); %>
         <TR>
            <TD><%= anOrderItem.getNum() %></TD>
            <TD><%= anOrderItem.getName() %></TD>
            <TD><%= anOrderItem.getPrice() %>円</TD>
            <TD><%= anOrderItem.getCategory() %></TD>
            <TD><IMG src="<%= sb.substring(0, sb.lastIndexOf("/")) %><%= anOrderItem.getImage() %>"></TD>
            <TD><INPUT TYPE="text" NAME="<%= anOrderItem.getNum() + "_quantity" %>"
                       VALUE="<%= anOrderItem.getOrderQuantity() %>" SIZE=1></TD>
          </TR>
<%      } %>

  </TBODY>
</TABLE>
<BR>
<BR>
<INPUT TYPE="submit" NAME="putIntoOrder" VALUE="選択した商品を購入する">
</FORM>
</BODY>
</HTML>
--------------------------------------------------------


これらのソース・コードの変更点の詳細は次回説明しますが、
ここで行った変更は、ショッピング・カートの内容をセッション・
オブジェクトに記憶させ、セッション・オブジェクトの
中からショッピング・カートの内容を取り出してWebページに
表示するようにするものです。



では、EclipseでTomcatを起動し、テストしてみましょう。

Webブラウザーを起動し、下記のURLを入力してください。

http://localhost:8080/JStudy2/itemSelect.html

(1) 「キーワード」欄、「カテゴリー」欄ともに何も入力せずに
「送信」ボタンをクリックしてみてください。

(2) 次のページ(商品リストのページ)で商品を一つ選択(チェッ
ク・マークを入れる)し、「選択した商品をカートに入れる」ボタン
をクリックします。

(3) 選択した商品が次のページ(ショッピング・カートのページ)で
リストされていることを確認したあと、Webブラウザーの「戻る」
ボタンをクリックして前のページ(商品リストのページ)に戻り
ます。

(4) 前に選択した商品にチェック・マークがついたままになって
いると思いますが、そのチェック・マークをはずし(ただし、どの
商品にチェック・マークをつけていたのかは覚えておいてください)、
また別の商品を選択(チェック・マークを入れる)してから、
「選択した商品をカートに入れる」ボタンをクリックします。
(あるいはもっと前のページまで戻ってから再度この商品リストの
ページまで進めて操作してもかまいません。どういう違いがある
のか、なぜそういう違いが生じるのかも考えてみてください。)

(5) 前に(2)で選択していた商品と(4)で新たに選択した商品の両方
がショッピング・カートのページにリストされていることを確認
してください。
このように、たとえ(4)では選択されていなくても(2)で選択され
ていればセッション・オブジェクトに記憶されているために
ショッピング・カートのページにリストされるのです。

あと同様に、何回か(3)〜(5)の操作を繰り返して、ショッピング・
カートにどんどんと商品が追加されていくことを確認してください。



では、今日はここまでにします。


(次回に続く)


何か、わからないところがありましたら、下記のWebページまで質問を
お寄せください。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2008 Future Lifestyle Inc. 不許無断複製