広告 |
---|
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 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. 不許無断複製 |