■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2009年04月20日

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

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


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


========================================================
◆ 01.SOAPのアプリケーション(Webサービス)
========================================================


前回作成したRoomBookDbManagerクラスは、ちょっと内容を間違えていました。
(いつも締め切り間際になってから慌てて原稿を書いているからこんな間違い
をおこしてしまうんですね。・・・反省。)
このクラスのgetBookedRoomNum()メソッドは、引数のroomNum, stayDateに、
それぞれ、予約済みかどうかを検索したい部屋の部屋番号と宿泊予定日を
指定し、その部屋がその宿泊予定日に予約済みかどうかを調べるためのもの
ですが、この結果として部屋番号のリスト(Vector型)を返すことは無意味
ですね。部屋番号は引数(roomNum)に指定してあるので結果としてリストを
返す必要はありませんし、知りたいことは予約済みかどうか、ということなの
で、結果としてはtrueかfalse、すなわちboolean型を返すのが妥当です。
そうすると、getBookedRoomNum()というメソッド名も適切ではありませんね。
メソッド名も変えたほうがいいでしょうね。

という訳で、RoomBookDbManagerクラスを下記のように修正しておきましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.webservice.hotel.db;

import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.GregorianCalendar;

import org.apache.log4j.Logger;

public class RoomBookDbManager extends DbManager {
   private static final String selectRoomnumAndDateSql = "SELECT * FROM ROOMBOOK WHERE ROOMNUM = ? AND DATE = ?";
   private static Logger logger = Logger.getLogger(RoomBookDbManager.class);

   public static boolean isBooked(int roomNum, GregorianCalendar stayDate) throws HotelDbException {
      logger.info("Start ...............");
      boolean booked = false;
      try {
         PreparedStatement selectPs = null;
         ResultSet rs;
         selectPs = conn.prepareStatement(selectRoomnumAndDateSql);
         selectPs.setInt(1, roomNum);
         selectPs.setDate(2, new Date(stayDate.getTimeInMillis()));
         rs = selectPs.executeQuery();
         while (rs.next()) {
            booked = true;
         }
         selectPs.close();
      }
      catch (SQLException e) {
         logger.error(e);
         throw new HotelDbException("Error: isBooked() failed!", e);
      }
      finally {
         logger.info("End ...............");
      }
      return booked;
   }

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



では、これらのクラスを使って

(H_1) ホテルの空き部屋の検索:
(H_2) ホテルの客室の予約:

を実行できるように、HotelSoapBindingImplクラスを編集しましょう。

HotelSoapBindingImplクラスを下記のように編集してみてください。

--------------------------------------------------------
/**
 * HotelSoapBindingImpl.java
 *
 * このファイルはWSDLから自動生成されました / [en]-(This file was auto-generated from WSDL)
 * Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java生成器によって / [en]-(by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.)
 */

package jp.co.flsi.lecture.webservice.hotel;

import java.rmi.RemoteException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Vector;
import java.util.regex.Pattern;

import jp.co.flsi.lecture.webservice.hotel.db.BookingDbManager;
import jp.co.flsi.lecture.webservice.hotel.db.DbManager;
import jp.co.flsi.lecture.webservice.hotel.db.HotelDbException;
import jp.co.flsi.lecture.webservice.hotel.db.RoomBookDbManager;
import jp.co.flsi.lecture.webservice.hotel.db.RoomInfoDbManager;

import org.apache.log4j.Logger;

public class HotelSoapBindingImpl implements Hotel{
  
   private static Logger logger = Logger.getLogger(HotelSoapBindingImpl.class);
  
   public RoomInfo[] findRooms(StayInfoInput stayInfo) throws RemoteException {
      logger.info("Start ...............");
      Vector<RoomInfo> roomInfoVector = null;

      try {
         String startDate = stayInfo.getStartDate();
         String year;
         String month;
         String dayOfMonth;
         if (Pattern.matches("2[0-9]{3}[0-1][0-9][0-3][0-9]", startDate)) {
            year = startDate.substring(0, 4);
            month = startDate.substring(4, 6);
            dayOfMonth = startDate.substring(6, 8);
         }
         else {
            logger.info("Invalid parameter: startDate (" + startDate + ") is invalid.");
            return new RoomInfo[] {};
         }
         GregorianCalendar stayDate = new GregorianCalendar(Integer.parseInt(year),
                                                Integer.parseInt(month),
                                                Integer.parseInt(dayOfMonth));
         int numOfNights = stayInfo.getNumOfNights();
         int numOfLodgers = stayInfo.getNumOfLodgers();
         if (numOfNights < 1 || numOfLodgers < 1) {
            logger.info("Invalid parameter: numOfNights (" + numOfNights + ") or numOfLodgers ("
                     + numOfLodgers + ") is invalid.");
            return new RoomInfo[] {};
         }

         DbManager.connect();
         roomInfoVector = RoomInfoDbManager.getData(numOfLodgers, stayInfo.getMinRatePerNight(),
                                          stayInfo.getMaxRatePerNight());
         int roomInfoSize = roomInfoVector.size();
         if (roomInfoSize > 0) {
            for (int roomIndex = roomInfoSize - 1; roomIndex > -1; roomIndex--) {
               RoomInfo roomInfo = roomInfoVector.get(roomIndex);
               int roomNum = roomInfo.getRoomNum();
               for (int i = 0; i < numOfNights; i++) {
                  if (RoomBookDbManager.isBooked(roomNum, stayDate)) {
                     roomInfoVector.remove(roomIndex);
                     break;
                  }
                  stayDate.add(Calendar.DAY_OF_MONTH, 1);
               }
            }
         }
      } catch (HotelDbException e) {
         logger.error(e);
      }
      finally {
         DbManager.disconnect();
         logger.info("End ...............");
      }
      return roomInfoVector.toArray(new RoomInfo[roomInfoVector.size()]);
   }

   public boolean reserveRoom(RoomReserveInfo roomReserve) throws RemoteException {
      logger.info("Start ...............");
      try {
         String startDate = roomReserve.getStartDate();
         String year;
         String month;
         String dayOfMonth;
         if (Pattern.matches("2[0-9]{3}[0-1][0-9][0-3][0-9]", startDate)) {
            year = startDate.substring(0, 4);
            month = startDate.substring(4, 6);
            dayOfMonth = startDate.substring(6, 8);
         }
         else {
            logger.info("Invalid parameter: startDate (" + startDate + ") is invalid.");
            return false;
         }
         GregorianCalendar startStayDate = new GregorianCalendar(Integer.parseInt(year),
                                                   Integer.parseInt(month),
                                                   Integer.parseInt(dayOfMonth));
         int numOfNights = roomReserve.getNumOfNights();
         int numOfLodgers = roomReserve.getNumOfLodgers();
         if (numOfNights < 1 || numOfLodgers < 1) {
            logger.info("Invalid parameter: numOfNights (" + numOfNights + ") or numOfLodgers ("
                     + numOfLodgers + ") is invalid.");
            return false;
         }
         DbManager.connect();
         BookingDbManager.insertData(roomReserve.getRoomNum(), roomReserve.getName(),
                              roomReserve.getAddress(), roomReserve.getTelNo(),
                              startStayDate, numOfNights, numOfLodgers);
      } catch (HotelDbException e) {
         logger.error(e);
      }
      finally {
         DbManager.disconnect();
         logger.info("End ...............");
      }
      return true;
   }

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

なお、今までは自動生成されたコードをそのまま使っていて、非常に長ったらしい行
があったりしましたが、それらも今回は(import文を追加することによって)
整理し直してあります。以前のソース・コードと比較してみてください。


上記のソース・コードのうち、

if (Pattern.matches("2[0-9]{3}[0-1][0-9][0-3][0-9]", startDate)) {

という行に注目してください。

このPatternというクラスは、文字列のパターンをチェックするためのクラスで、
このmatches()メソッドの第一引数には、チェックしたいパターンを正規表現で
指定し、第二引数には文字列を指定します(正規表現とは何ぞや、については、
また、後ほどまとめて詳しくお話します)。そうするとその文字列が指定された
パターンに一致するとtrueが返され、一致しないとfalseが返されます。
ここでは、

"2[0-9]{3}[0-1][0-9][0-3][0-9]"

というパターンが指定されていますが、ここで[0-9]は0から9までのいずれかの数字
1文字を意味し、{3}は3回(3文字)繰り返すことを意味します。つまり、[0-9]{3}は
0から9までの数字を3文字分並べることを意味します。
また、先頭の2はずばり先頭の1文字が2でなければならないことを意味します。
したがって、

"2[0-9]{3}[0-1][0-9][0-3][0-9]"

というパターンは、先頭1文字が2で、次に0から9までのいずれかの数字が3文字並び、
続いて0から1までのいずれかの数字が1文字並び、続いて0から9までのいずれかの数字
が1文字並び、続いて0から3までのいずれかの数字が1文字並び、続いて0から9までの
いずれかの数字が1文字並ぶ、というパターンを意味します。
たとえば、

20090420

とか

20111231

とかいった数字がこのパターンに該当し、要するに年月日を表す数字であることを
チェックするためのものです。
でも、ちょっといい加減なチェックになっていますね。たとえば、

20110000

という数字でもtrueが返ってしまいますが、2011年00月00日なんて年月日はありま
せんから、本来ならfalseが返って欲しいものです。

実は、このような年月日などのパターンのチェックをしたいときには、正規表現を
使うよりも、JavaのAPIを利用したほうが、厳密なチェックがずっと簡単にできてしまい
ますので、その方法を後で説明いたします。


さて、この

if (Pattern.matches("2[0-9]{3}[0-1][0-9][0-3][0-9]", startDate)) {

という行や、

if (numOfNights < 1 || numOfLodgers < 1) {

という行は、入力された引数の妥当性をチェックしていますね。
そして、不適切な引数が指定されていた場合は、データベースへのアクセスなどは
せずに、直ちに

return new RoomInfo[] {};

によって、空の配列を返してメソッドを終了しています。
引数の指定が不適切だから、該当するデータは無いというわけです。


こういった、入力値の条件の取り扱い方については、契約による設計(Design By Contract)
という考え方があります。


(次回に続く)


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



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