■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2008年02月10日

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

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


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


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


では、セッション・オブジェクトを使用するために行った
前回のソース・コードの変更内容を説明しておきましょう。

まず、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);
   }

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

このサーブレットは、指定された商品をショッピング・カートに追加し、
ショッピング・カートの中身を表示するJSPを呼び出すためのものですが、
前回このソース・コードに行った変更の内容は、以下の通りです。


・指定された商品をショッピング・カートに追加する前に、すでに
セッション・オブジェクトにショッピング・カートのオブジェクト
が保存されていればそれを取り出し、まだセッション・オブジェクトに
保存されていない場合は新規にショッピング・カートのオブジェクト
(= Orderオブジェクト)を生成する。

・(これは以前のソース・コードに書かれている部分だが)Webページで
選択された商品をショッピング・カートのオブジェクト(Orderオブジェクト)
に追加する。

・最後に、ショッピング・カートのオブジェクト(anOrder)をセッション・
オブジェクトに保存する。


この変更されたソース・コードでは、最初にセッション・オブジェクトを
取り出すために以下のコードを追加しています。

HttpSession session = req.getSession();

これは、前回説明したHttpServletRequestのgetSession()メソッド
によってセッション・オブジェクトを取り出す、あるいは、まだ
セッション・オブジェクトが存在しないときは新規に生成する
ためのコードですね。

セッション・オブジェクトは、いったん生成されると、セッション
を終了させない限り、Webコンテナー(サーブレット・コンテナー)
内に維持されます。
セッション・オブジェクトは、個々のクライアント(Webブラウザー)
ごとに別々に用意されており、インターネットにつながっている
複数のクライアントからサーブレット(あるいはJSP)に同時に要求が
来ても、ちゃんと個々のクライアントに対応したセッション・オブジェ
クトが取り出されるようになっています。

このセッション・オブジェクトに、必要なオブジェクトを保存させる
ためには、setAttribute()というメソッドを使います。

このメソッドには、String型の引数と、Object型の引数があり、
String型の引数には保存させたいオブジェクトの属性名を指定し、
Object型(Objectはすべてのクラスのスーパークラスであるから、
どんなクラスの型でも引数に指定できる)の引数には保存させたい
オブジェクトを指定します。

このとき、もし同名の属性名のオブジェクトがすでに保存されて
いる場合は、それが新たに指定したオブジェクトに置き換えられ
てしまいますので、ご注意ください。

上のソース・コードでは、最後のほうの

session.setAttribute("ORDER",anOrder);

という行で、このメソッドを呼び出しています。すなわち「ORDER」
という属性名でanOrder(Orderオブジェクト=ショッピング・カート
のオブジェクト)を保存しています。
(この行は、以前は
req.setAttribute("ORDER",anOrder);
つまり、HttpServletRequestのsetAttribute()メソッドを呼び出して
いた行を書き換えたものであることに注意してください。
HttpServletRequestオブジェクトはあくまでWebブラウザーからの要求
を表現する一過性のオブジェクトであり、つまりサーブレットやJSPでの
処理が終了すると消えてしまうので、その代わりにセッション・オブジェ
クトを使うのです。)


逆に、保存しておいたオブジェクトをセッション・オブジェクトから
取り出したいときには、getAttribute()というメソッドを使います。
このメソッドには、String型の引数があり、保存しておいたオブジェク
トの属性名を指定します。

上のソース・コードでは、

Order anOrder = (Order)session.getAttribute("ORDER");

という行でgetAttribute()メソッドを使って、セッション・オブジェ
クトから「ORDER」という属性名のオブジェクトを取り出しています。
(なお、getAttribute()メソッドの戻り値はObject型ですが、実際に
保存しておいたオブジェクトはOrder型なので、Order型にキャストし
ています。)

そうすると、「ORDER」という属性名のオブジェクト(ショッピング・
カートのオブジェクト)が予め保存されていればそれがanOrder変数に
代入されます。
しかし、予め保存されていなかった場合はanOrderの値はnullになります。

つまりこのOrderItemServletの呼び出しが今回はじめてで、まだOrderオブ
ジェクト(ショッピング・カートのオブジェクト)が作られていなかった
場合は、セッション・オブジェクトに保存されていないので、新規にOrder
オブジェクトを作成してセッション・オブジェクトに保存してやる必要が
あります。
そのために、その次の行の

if (anOrder == null) anOrder = new Order();

によって、anOrderの値がnullのときには新規にOrderオブジェクトを生成
するようにしています。



では、次に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>
--------------------------------------------------------

このソース・コードでは

<jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Order" id="ORDER" scope="session" />

の行が以前の

<jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Order" id="ORDER" scope="request" />

という行から変更されたことに注意してください。

変わったのはscope属性の値が"session"になったことだけです。
vol.078でもお話したように、scope属性の値として"session"を指定すると、
このBeanのインスタンス(ここではOrderオブジェクト)はセッション・
オブジェクト(HttpSessionオブジェクト)から探し出されることになります。

あとは何も変わっていませんね。



ところで、OrderItemServletのほうはもう少し修正しておく必要があり
ますね。
もし、すでにOrderオブジェクト(ショッピング・カートのオブジェクト)
に商品を追加した状態で再度Webページから同じ商品を選択してショッピ
ング・カートに追加すると、今のOrderItemServletではOrderオブジェクト
に同じ商品のOrderItemオブジェクトが複数個追加されてしまいます。
その結果、次にショッピング・カートのWebページを開くと、同じ商品が
別々の行に複数リストされてしまいます。
これでは冗長ですから、同じ商品の場合は、一つのOrderItemオブジェクト
に整理し直して、そのorderQuantityの値を変更するようにしましょう。

この類の整理は、TreeMapというクラス(java.utilパッケージにはいっている)
などを使えば簡単に行えるのですが、今回は(勉強のために)いちいち自分
でプログラミングしてみましょう。

では、Orderクラスを下記のように修正してみてください。
--------------------------------------------------------
package jp.co.flsi.lecture.webapp.entity;

import java.util.Date;
import java.util.Vector;

public class Order {
   private int orderNumber;
   private String customerNum;
   private Date orderDate;
   private boolean payment;
   private short delivery;
   private Vector<OrderItem> orderItems;
   private boolean orderComplete;
  
   public Order() {
      setOrderNumber(0);         // 0は未設定を意味するものとする。
      setPayment(false);        // デフォルトは未払い。
      setDelivery((short)0);    // デフォルトは未配達。
      setOrderComplete(false);  // デフォルトは注文前
      orderItems = new Vector<OrderItem>();
   }
  
   public void setOrderNumber(int orderNumber) {
      this.orderNumber = orderNumber;
   }

   public int getOrderNumber() {
      return orderNumber;
   }
  
   public void setCustomerNum(String customerNum) {
      this.customerNum = customerNum;
   }

   public String getCustomerNum() {
      return customerNum;
   }
  
   public void setOrderDate(Date orderDate) {
      this.orderDate = orderDate;
   }
  
   public Date getOrderDate() {
      return orderDate;
   }
  
   public void setPayment(boolean payment) {
      this.payment = payment;
   }
  
   public boolean isPayment() {
      return payment;
   }
  
   public void setDelivery(short delivery) {
      this.delivery = delivery;
   }

   public short getDelivery() {
      return delivery;
   }

   public void setOrderItems(Vector<OrderItem> orderItems) {
      this.orderItems = orderItems;
   }
  
   public Vector<OrderItem> getOrderItems() {
      return orderItems;
   }

   public void addOrderItem(OrderItem anOrderItem) {
      OrderItem oi = searchSameOrderItem(anOrderItem);
      if (oi != null) {
         oi.setOrderQuantity(oi.getOrderQuantity() + anOrderItem.getOrderQuantity());
      }
      else {
         this.orderItems.add(anOrderItem);
      }
   }
  
   public void removeOrderItem(OrderItem anOrderItem) {
      for (int i = 0; i < this.orderItems.size(); i++) {
         if (this.orderItems.get(i).getNum().equals(anOrderItem.getNum())) {
            this.orderItems.remove(i);
         }
      }
   }
  
   public void setOrderComplete(boolean orderComplete) {
      this.orderComplete = orderComplete;
   }

   public boolean isOrderComplete() {
      return orderComplete;
   }

   private OrderItem searchSameOrderItem(OrderItem anOrderItem) {
      for (OrderItem oi : this.orderItems) {
         if (oi.getNum().equals(anOrderItem.getNum())) return oi;
      }
      return null;
   }
}
--------------------------------------------------------

このソース・コードでは、新たにsearchSameOrderItem()というメソッド
が追加され、addOrderItem()メソッドが変更されています。

ここらへんのソース・コードも、もう自力で理解できるでしょう。

searchSameOrderItem()メソッドは、引数に指定されたOrderItemオブジェ
クトと同じ商品番号を持つ商品がOrderオブジェクトの中にはいっているか
どうか調べ、はいっていればその商品のOrderItemオブジェクトを戻り値と
して返すものですね。

addOrderItem()メソッドは、このsearchSameOrderItem()メソッドを利用す
ることにより、引数に指定されたOrderItemオブジェクトと同じ商品番号の
商品がOrderオブジェクトの中にはいっていればその商品の注文数(購入個数)
を引数のOrderItemオブジェクトの注文数分だけ増やし、はいっていなければ
引数のOrderItemオブジェクトを新たにOrderオブジェクトに追加するもの
ですね。


では、この新しいOrderが適切に作動するかどうかテストしてみましょう。

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

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

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

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

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

(4) 前に選択した商品にチェック・マークがついたままになって
いると思いますが、そのチェック・マークがついたままにして、
また別の商品も選択(チェック・マークを入れる)してから、
「選択した商品をカートに入れる」ボタンをクリックします。

(5) 前に(2)で選択していた商品と(4)で新たに選択した商品の両方
がショッピング・カートのページにリストされていることを確認
してください。
このとき(2)でも(4)でもともに選択されていた商品も1行だけに
まとめられており、その購入個数が選択した回数分(現在のところ
2)になっていることを確認してください。

あと同様に、何回か(3)〜(5)の操作を繰り返して、ショッピング・
カートのリストが意図通りになっていることを確認してください。
同じ商品はちゃんと1行にまとめられていますね。



では、次にcart.jspの中で指定されていたorderprocessに対応する
サーブレットを作成することにしましょう。
クラス名はOrderProcessServletにします。

cart.jspが表示するWebページでは、商品の購入個数が変更
できましたから、OrderProcessServletでは、この購入個数
をチェックします。
このとき、購入個数が0になっている商品は購入しないという
意味だから、Orderオブジェクトの中から取り除きましょう。

下記のようにOrderProcessServletクラスを作成してみてくだ
さい。
--------------------------------------------------------
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.entity.Order;
import jp.co.flsi.lecture.webapp.entity.OrderItem;

public class OrderProcessServlet 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");
      Enumeration paramNames = req.getParameterNames();
      while(paramNames.hasMoreElements()) {
         String aParamName = (String)paramNames.nextElement();
         if (!aParamName.equals("putIntoOrder")) {
            OrderItem anOrderItem = new OrderItem();
            for (OrderItem oi : anOrder.getOrderItems()) {
               if(aParamName.substring(0, 5).equals(oi.getNum())) {
                  anOrderItem.setNum(oi.getNum());
                  anOrderItem.setName(oi.getName());
                  anOrderItem.setPrice(oi.getPrice());
                  anOrderItem.setImage(oi.getImage());
                  anOrderItem.setCategory(oi.getCategory());
                  anOrderItem.setOrderQuantity(Integer.parseInt((String)req.getParameter(aParamName)));
               }
            }
            anOrder.removeOrderItem(anOrderItem);
            if (anOrderItem.getOrderQuantity() > 0) {
               anOrder.addOrderItem(anOrderItem);
            }
         }
      }
      session.setAttribute("ORDER",anOrder);
      getServletContext().getRequestDispatcher("/order.jsp").forward(req,res);
   }


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


このサーブレットをweb.xmlに登録しましょう。次のようにweb.xmlを編集してください。
--------------------------------------------------------
<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   version="2.5">

    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>jp.flsi.lecture.servlet.TestServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>TestServlet2</servlet-name>
        <servlet-class>jp.flsi.lecture.servlet.TestServlet2</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>AgeCalcServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.AgeCalcServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>CustomerServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.CustomerServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>ItemServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.ItemServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>OrderItemServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.OrderItemServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>OrderProcessServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.OrderProcessServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/servlet/test</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>TestServlet2</servlet-name>
        <url-pattern>/servlet/test2</url-pattern>
    </servlet-mapping>
   
    <servlet-mapping>
        <servlet-name>AgeCalcServlet</servlet-name>
        <url-pattern>/servlet/age</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>CustomerServlet</servlet-name>
        <url-pattern>/servlet/customers</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>ItemServlet</servlet-name>
        <url-pattern>/servlet/items</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>OrderItemServlet</servlet-name>
        <url-pattern>/servlet/orderitems</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>OrderProcessServlet</servlet-name>
        <url-pattern>/servlet/orderprocess</url-pattern>
    </servlet-mapping>
   
</web-app>
--------------------------------------------------------


続いて、下記のようにorder.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="confirmorder" method="POST">
<TABLE border="1" width="100%">
  <TBODY>
    <TR>
      <TH>商品番号</TH>
      <TH>商品名</TH>
      <TH>価格</TH>
      <TH>カテゴリー</TH>
      <TH>購入個数</TH>
    </TR>

<%@ page import="jp.co.flsi.lecture.webapp.entity.*" %>
<% int totalPrice = 0; %>
<jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Order" id="ORDER" scope="session" />
<%    OrderItem anOrderItem;
      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 align="right"><%= anOrderItem.getOrderQuantity() %></TD>
          </TR>
<%         totalPrice += anOrderItem.getPrice() * anOrderItem.getOrderQuantity();
      } %>

  </TBODY>
</TABLE>
<BR>
<TABLE border="1">
    <TR>
      <TH>合計金額:</TH>
      <TH><%= totalPrice %>円</TH>
    </TR>
</TABLE>
<BR>
<BR>
ユーザーIDとパスワードをご入力ください。
<TABLE border="0">
    <TR>
      <TH>ユーザーID:</TH>
      <TD><INPUT TYPE="text" NAME="userId" VALUE="" SIZE=20></TD>
    </TR>
    <TR>
      <TH>パスワード:</TH>
      <TD><INPUT TYPE="password" NAME="password" VALUE="" SIZE=20></TD>
    </TR>
</TABLE>
<BR>
<BR>
<INPUT TYPE="submit" NAME="confirmOrder" VALUE="最終購入確認画面に進む">
</FORM>
</BODY>
</HTML>
--------------------------------------------------------

これらOrderProcessServletクラスとorder.jspのソース・コードの
説明は次回行います。


ところで、このTomcatアプリケーションをテストしてみると、操作内容
によってはエラーが発生します。
しかし、tomcatPluginを使ってEclipseでTomcatを起動しているおかげ
で、このエラーの発生源はEclipseを使って簡単に調べることができ
ます。

というわけで、実際にエラーを発生させて、その発生源の調べ方および
対処方法を説明していきましょう。


(次回に続く)


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

何か、わからないところがありましたら、下記の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. 不許無断複製