広告

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2010年04月04日

    Java総合講座 - 初心者から達人へのパスポート
                  vol.198

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


-------------------------------------------------------
・現在、このメールマガジンは以下の2部構成になっています。
[1] 当初からのコース:毎週日曜の夜に発行
   これは現在、中級レベルになっています。
[2] 2009年11月開講コース:毎週水曜の夜に発行
   これは現在、初心者向けのレベルになっています。
・このメールマガジンは、画面を最大化して見てください。
小さな画面で見ていると、不適切な位置で行が切れてしまう
など、問題を起すことがあります。
・このメールマガジンに掲載されているソース・コード及び
文章は特に断らない限り、すべて筆者が著作権を所有してい
ます。また、これらのソース・コードは学習用のためだけに
提供しているものです。
-------------------------------------------------------


========================================================
◆ 01.Strutsのアプリケーション開発(プロジェクト:StrutsShop)
========================================================


前回、UserDbManager.javaをプログラミングしたときに、例外発生時にデータベース
をロールバックするコードを入れておくのを忘れていましたので、UserDbManager.java
を下記のように修正して下さい。

--------------------------------------------------------
package jp.co.flsi.lecture.struts.db;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import jp.co.flsi.lecture.reflect.ValueLogStringMaker;
import org.apache.log4j.Logger;

public class UserDbManager extends DbManager {
   private static Logger logger = Logger.getLogger(UserDbManager.class);
   private String selectMaxTempUseridSql = "SELECT MAX(USERID) FROM USER WHERE U_TYPE = 'T'";
   private static final String selectUserTypeAndUseridSql = "SELECT * FROM USER WHERE U_TYPE = ? AND USERID = ?";
   private static final String insertSql = "INSERT INTO USER VALUES (?, ?, ?, ?, ?, ?, ?, ?)";

   public String getMaxTemporaryUserid() throws StruShopDbException {
      logger.info("Start ...............");
      ValueLogStringMaker stringMaker = new ValueLogStringMaker();
      String maxTempUserid = null;
      try {
         Statement selectSt = conn.createStatement();
         ResultSet rs = selectSt.executeQuery(selectMaxTempUseridSql);
         if (rs.next()) maxTempUserid = rs.getString(1);
         if (maxTempUserid == null) maxTempUserid =  "0000000";
         selectSt.close();
      }
      catch (SQLException e) {
         logger.error(e, e);
         throw new StruShopDbException("Error: getMaxTemporaryUserid() failed!", e);
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         logger.info("End ...............");
      }
      logger.info("Method return: <<<<<" + stringMaker.getValues("maxTempUserid", maxTempUserid));
      logger.info(">>>>>");
      return maxTempUserid;
   }

   public User getDataByUserTypeAndUserid(String userType, String userid) throws StruShopDbException {
      logger.info("Start ...............");
      ValueLogStringMaker stringMaker = new ValueLogStringMaker();
      logger.info("Method parameter: <<<<<" + stringMaker.getValues("userType", userType));
      logger.info(">>>>>");
      logger.info("Method parameter: <<<<<" + stringMaker.getValues("userid", userid));
      logger.info(">>>>>");
      User user = new User();
      try {
         PreparedStatement selectPs = null;
         ResultSet rs;
         if (userType == null) {
            userType = "";
         }
         if (userid == null) {
            userid = "";
         }
         selectPs = conn.prepareStatement(selectUserTypeAndUseridSql);
         selectPs.setString(1, userType);
         selectPs.setString(2, userid);
         rs = selectPs.executeQuery();
         while (rs.next()) {
            user.setUserType(rs.getString("U_TYPE"));
            user.setUserid(rs.getString("USERID"));
            user.setPassword(rs.getBytes("PASS"));
            user.setName(rs.getString("NAME"));
            user.setZipcode(rs.getString("ZIPCODE"));
            user.setAddress(rs.getString("ADDRESS"));
            user.setTelno(rs.getString("TELNO"));
            user.setEmail(rs.getString("EMAIL"));
         }
         selectPs.close();
      }
      catch (SQLException e) {
         logger.error(e, e);
         throw new StruShopDbException("Error: getDataByUserTypeAndUserid() failed!", e);
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         logger.info("End ...............");
      }
      logger.info("Method return: <<<<<" + stringMaker.getValues("user", user));
      logger.info(">>>>>");
      return user;
   }

   public boolean insertData(User user) throws StruShopDbException {
      logger.info("Start ...............");
      ValueLogStringMaker stringMaker = new ValueLogStringMaker();
      logger.info("Method parameter: <<<<<" + stringMaker.getValues("user", user));
      logger.info(">>>>>");
      try {
         conn.setAutoCommit(false);
         PreparedStatement insertPs = conn.prepareStatement(insertSql);
         insertPs.setString(1, user.getUserType());
         insertPs.setString(2, user.getUserid());
         insertPs.setBytes(3, user.getPassword());
         insertPs.setString(4, user.getName());
         insertPs.setString(5, user.getZipcode());
         insertPs.setString(6, user.getAddress());
         insertPs.setString(7, user.getTelno());
         insertPs.setString(8, user.getEmail());
         insertPs.executeUpdate();
         insertPs.close();
         conn.commit();
      }
      catch (SQLException e) {
         try {
            conn.rollback();
            logger.info("Rollback executed.");
         }
         catch (Exception ex) {
            logger.info("Rollback failed.", ex);
         }
         logger.error(e, e);
         throw new StruShopDbException("Error: insertData() failed!", e);
      }
      catch (Throwable e) {
         try {
            conn.rollback();
            logger.info("Rollback executed.");
         }
         catch (Exception ex) {
            logger.info("Rollback failed.", ex);
         }
         logger.error(e, e);
         return false;
      }
      finally {
         logger.info("End ...............");
      }
      logger.info("Method return: <<<<< true");
      logger.info(">>>>>");
      return true;
   }

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

修正したのはinsertData()メソッドですが、どこを修正したか分かりますね。



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


さて、USERテーブルのPASSカラムはパスワードを保管するためのものですが、
パスワードは秘密にしておくべきデータですから、当カラムには暗号化して保管
します。
(実は、暗号化したデータを入れられるようにするために、PASSカラムをvarbinary
という特殊な型にしておいたのです。)

そのために、パスワードの暗号化を行うクラスが必要になりますので、それを
PasswordEncryptionというクラス名で作成することにしましょう。

(1) Eclipseのプロジェクト・エクスプローラー内のStrutsShop(プロジェクト)を
右クリックし、「新規」→「パッケージ」を選択します。

(2) 「新規Javaパッケージ」ウインドウが開いたら、「名前」欄に

jp.co.flsi.lecture.security

と入力し、「完了」ボタンをクリックしましょう。


これで、StrutsShop(プロジェクト)配下の「Javaリソース: src」配下に
jp.co.flsi.lecture.securityパッケージが作成されているので、
PasswordEncryptionクラスの作成に取りかかりましょう。

(1) プロジェクト・エクスプローラー内でjp.co.flsi.lecture.securityを右クリック
し、「新規」→「クラス」を選択します。

(2) 「名前」欄に

PasswordEncryption

と入力し、「完了」ボタンをクリックします。

(3) PasswordEncryption.javaのエディターが開いたら、下記のように編集しましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.security;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import org.apache.log4j.Logger;

public class PasswordEncryption {
   private static Logger logger = Logger.getLogger(PasswordEncryption.class);
   private static byte[] key = new byte[]{(byte) 0xFF, (byte) 0xA1, (byte) 0x13, (byte) 0xBC, (byte) 0x72, (byte) 0xF8, (byte) 0x1D, (byte) 0xFF};

   public static byte[] encrypt(String password) {
      logger.info("Start ...............");
      byte[] encrypted = null;
      try {
         SecretKey secretKey = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(key));
         Cipher cipher = Cipher.getInstance("DES");
         cipher.init(Cipher.ENCRYPT_MODE, secretKey);
         encrypted = cipher.doFinal(password.getBytes());
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         logger.info("End ...............");
      }
      return encrypted;
   }

   public static boolean checkWithEncryptedPassword(String password, byte[] encryptedPassword) {
      logger.info("Start ...............");
      boolean result = false;
      try {
         SecretKey secretKey = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(key));
         Cipher cipher = Cipher.getInstance("DES");
         cipher.init(Cipher.DECRYPT_MODE, secretKey);
         String decrypted = new String(cipher.doFinal(encryptedPassword));
         if (decrypted.equals(password)) result = true;
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         logger.info("End ...............");
      }
      return result;
   }

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

このソース・コードのうち、encrypt()というメソッドは引数に指定されたパスワード
の暗号化を行うメソッドで、checkWithEncryptedPassword()というメソッドは引数に
指定されたパスワードと暗号化されたパスワードを照合して同じものかどうか調べる
メソッドです。

なお、途中で"DES"という言葉が出てくることから分かるように、このソース・コード
ではDESという共通鍵暗号方式を使ってパスワードの暗号化と復号化(暗号化と復号化
は同じ仕組みが使われている)を行っていますが、暗号化などのセキュリティー関係
の技術については後の上級コースでお話しますので、ここでは上記のソース・コードの
解説はしません。雰囲気だけ味わって下さい。

なお、現実のアプリケーションでは、もっと強力な暗号化技術が使われていますが、こ
れも上級コースで紹介します。



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


では、続いてConfirmOrderActionクラスを編集しましょう。

ConfirmOrderAction.javaを下記のように編集して下さい。

--------------------------------------------------------
package jp.co.flsi.lecture.struts;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Vector;

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

import jp.co.flsi.lecture.reflect.ValueLogStringMaker;
import jp.co.flsi.lecture.security.PasswordEncryption;
import jp.co.flsi.lecture.struts.db.Order;
import jp.co.flsi.lecture.struts.db.OrderItem;
import jp.co.flsi.lecture.struts.db.StruShopDbException;
import jp.co.flsi.lecture.struts.db.User;
import jp.co.flsi.lecture.struts.db.UserDbManager;

import org.apache.log4j.Logger;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class ConfirmOrderAction extends Action {
   private static Logger logger = Logger.getLogger(ConfirmOrderAction.class);

   public ActionForward execute(ActionMapping mapping, ActionForm form,
         HttpServletRequest request, HttpServletResponse response) {
      logger.info("Start ...............");
      UserDbManager userDbManager = new UserDbManager();
      try {
         ValueLogStringMaker stringMaker = new ValueLogStringMaker();
         logger.info("Method parameter: <<<<<" + stringMaker.getValues("mapping", mapping));
         logger.info(">>>>>");
         logger.info("Method parameter: <<<<<" + stringMaker.getValues("form", form));
         logger.info(">>>>>");
         logger.info("Method parameter: <<<<<" + stringMaker.getValues("request", request));
         logger.info(">>>>>");
         logger.info("Method parameter: <<<<<" + stringMaker.getValues("response", response));
         logger.info(">>>>>");
         HttpSession session = request.getSession();
         Order anOrder = (Order)session.getAttribute("ORDER");
         if (anOrder == null) {
            logger.info("Method return: <<<<< cartVacant (1)");
            logger.info(">>>>>");
            return mapping.findForward("cartVacant");
         }
         Vector<OrderItem> orderItemList = anOrder.getOrderItems();
         if (orderItemList == null) {
            logger.info("Method return: <<<<< cartVacant (2)");
            logger.info(">>>>>");
            return mapping.findForward("cartVacant");
         }
         OrderForm orderForm = (OrderForm) form;
         User user = new User();
         userDbManager.connect();
         String maxUserid = userDbManager.getMaxTemporaryUserid();
         user.setUserType("T");       // 一時的顧客のユーザー・タイプは"T"
         NumberFormat formatter = new DecimalFormat("0000000");
         user.setUserid(formatter.format(Integer.parseInt(maxUserid) + 1));
         user.setPassword(PasswordEncryption.encrypt("NONE"));
         user.setName(orderForm.getName());
         user.setZipcode(orderForm.getZipcode());
         user.setAddress(orderForm.getAddress());
         user.setTelno(orderForm.getTelNo());
         user.setEmail(orderForm.getEmailAddress());
         if (!userDbManager.insertData(user)) {
            logger.info("Method return: <<<<< userInsertError");
            logger.info(">>>>>");
            return mapping.findForward("userInsertError");
         }
         anOrder.setUserType(user.getUserType());
         anOrder.setUserId(user.getUserid());
         anOrder.setPayMethod(Integer.parseInt(orderForm.getPayMethod()));
         session.setAttribute("USER", user);
      } catch (StruShopDbException e) {
         logger.error(e, e);
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         try {
            userDbManager.disconnect();
         }
         catch (Exception e) {
            logger.info("Disconnect failed or there is no connection.");
         }
         logger.info("End ...............");
      }
      logger.info("Method return: <<<<< success");
      logger.info(">>>>>");
      return mapping.findForward("success");
   }

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

このソース・コードの内容は、独力で理解できることと思います。

なお、会員登録しないお客様はパスワードの入力は行いませんから、パスワード
を登録する必要もないのですが、上記のソース・コードでは、

user.setPassword(PasswordEncryption.encrypt("NONE"));

のように、"NONE"というパスワードをPasswordEncryptionで暗号化したものを
登録しようとしています。
この"NONE"というパスワードに意味はありませんが、USERテーブルのPASSカラムは
not nullの指定をしてあるため、何らかのデータを書き込む必要があります。そこ
で、ここでもuser.setPassword()メソッドを実行して何らかの値を設定しています。
別のもっと簡単なコードにしてもよかったのですが、ただ、こうしておけば、
PasswordEncryptionの使い方を示すサンプルになるという利点があります。

┌補足─────────────────────────┐
当メールマガジンのソース・コードの中には、

return mapping.findForward("success");

の中の"success"や、

user.setUserType("T");       // 一時的顧客のユーザー・タイプは"T"

の中の"T"のようにコンスタント(constant = 定数または固定値)
に決めた値がたくさん出てきますが、こういったコンスタントの
値は、現実の開発の現場ではインターフェース(interface)の
staticフィールドとして定義しておいて、そのインターフェース
を各クラスでimplementsして使用するという形式を取るのが普通
(これはプログラミング・ミスを減らすための方策の一つである)
です。
(上記のソース・コードのようにその場その場でリテラルで指定
していると、どこかで書き間違えが発生する恐れがあるが、イン
ターフェース(interface)に定義しておいてそれをimplements
するというやり方だとEclipseのコンテンツ・アシストの機能が
使えるので、プログラミング・ミスを防げる。)
しかし、当メールマガジンでは、ソース・ファイルの量をなる
べく少なくして簡単にするために、この作業は省略しています。
こういった補足的な話はそのうちに「ヒント&テクニックのコー
ナー」といったものを設けて、解説していこうかと思っています。
└───────────────────────────┘



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


では、続いてconfirmOrder.jspをコーディングしましょう。

confirmOrder.jspを下記のように編集しましょう。

--------------------------------------------------------
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-nested" prefix="nested" %>

<html:html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
      <title>購入(注文)内容の確認</title>
   </head>
   <body bgcolor="#77ffff" text="#fa5a00">
      <h1>購入する商品</h1>
         <table border="1" width="100%">
           <tbody>
             <tr>
               <th>商品番号</th>
               <th>商品名</th>
               <th>価格</th>
               <th>カテゴリー</th>
               <th>購入個数</th>
             </tr>
             <%@ page import="java.util.Vector" %>
             <%@ page import="jp.co.flsi.lecture.struts.db.OrderItem" %>
             <% String homebase = request.getContextPath(); %>
            <% int totalPrice = 0; %>
             <bean:define id="orderItemList" name="ORDER" property="orderItems" type="Vector<OrderItem>" scope="session" />
            <logic:iterate id="item" type="OrderItem" name="orderItemList" scope="page">
               <tr>
                  <td><bean:write name="item" property="itemNum" /></td>
                  <td><bean:write name="item" property="itemName" /></td>
                  <td><bean:write name="item" property="price" />円</td>
                  <td><bean:write name="item" property="catName" /></td>
                  <td align="center"><bean:write name="item" property="orderQuantity" /></td>
                </tr>
                <% totalPrice += item.getPrice() * item.getOrderQuantity(); %>
            </logic:iterate>
           </tbody>
         </table>
         <br>
         <table border="1">
             <tr>
               <th>合計金額:</th>
               <th><%= totalPrice %>円</th>
             </tr>
         </table>
         <br>
         <br>
         <h1>商品の送付先:</h1>
         <table border="1">
             <tr>
               <th>郵便番号</th>
               <th>住所</th>
               <th>氏名</th>
             </tr>
             <tr>
               <td><bean:write name="USER" property="zipcode" /></td>
               <td><bean:write name="USER" property="address" /></td>
               <td><bean:write name="USER" property="name" /></td>
             </tr>
         </table>
         <br>
         <br>
      <br>
      <html:link page="/itemSelect.jsp">トップ・ページ(商品検索のページ)に戻る</html:link>
   </body>
</html:html>
--------------------------------------------------------

これも、真新しいものは何もないので、説明不要ですね。



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


では、動作確認をしてみましょう。

(1) 「サーバー」ビューの中の「ローカル・ホストのTomcat v5.5サーバー」を
右クリックし、「開始」を選択します。しばらくして、

ローカル・ホストのTomcat v5.5サーバー[始動済み,同期済み]

というように、後ろに「始動済み」の表示が出たら、Tomcatの起動が完了しています。

(2) Webブラウザー(Internet Explorer)を起動して、URL

http://localhost:8080/StrutsShop/itemSelect.jsp

を入力しましょう。

(3) 「商品の検索」のWebページが開いたら、そのまま「商品検索」ボタンをクリック
して、次の「商品のリスト」のWebページでは全ての選択欄にチェックマークを入れて
「買物かごに入れる」ボタンをクリックしましょう。

(4) 「ショッピング・カートの中のリスト」のWebページが開いたら、そのまま
「これらの商品を購入する」ボタンをクリックしましょう。

(5) 「購入する商品」のWebページが開いたら、「お名前」欄、「郵便番号」欄、
「ご住所」欄、「お電話番号」欄、「Eメール・アドレス」欄に適当に入力し、
「お支払い方法」欄は「クレジット・カード払い」または「銀行振り込み」を選択し、
「最終購入確認画面に進む」ボタンをクリックしましょう。

ただし、「ご住所」欄の番地は「1-2-3」のようにハイフンを半角で入力し、全角では
入力しないで下さい。その理由は、あとでLinuxへデプロイする段階になったときに
まとめて説明します。


最後に購入(注文)内容の確認のWebページが開いて正しい内容が表示されることを
確認して下さい。

また、EclipseのDBViewerでUSERテーブルを表示して、正しくデータが登録されている
ことを確認して下さい。ただし、PASSカラムはバイナリー・データなので中身は表示
されません。

(DBViewerの操作方法を忘れた人は、vol.084などを復習のこと。)


◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


(次回に続く)


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



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      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) 2010 Future Lifestyle Inc. 不許無断複製