■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2009年06月21日

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

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


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


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


さて、では今度は、HotelSoapBindingImplのreserveRoom()メソッドのほうを
テストしてみましょう。

HotelClientのソース・コードを下記のように書き換えてみます。

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

import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;


public class HotelClient {
   public static void main(String[] args) throws RemoteException, ServiceException {
      HotelService locator = new HotelServiceLocator();
     
      Hotel hotel = null;
      String url = "http://localhost:8080/axis/services/Hotel";
      try {
         hotel = locator.getHotel(new URL(url));
      } catch (MalformedURLException e) {
         e.printStackTrace();
      }
      StayInfoInput stayInfo = new StayInfoInput();
      stayInfo.setStartDate("20091225");
      stayInfo.setNumOfLodgers(1);
      stayInfo.setNumOfNights(5);
      stayInfo.setMinRatePerNight(0);
      stayInfo.setMaxRatePerNight(50000);
      RoomInfo[] roomInfos = hotel.findRooms(stayInfo);
      if (roomInfos == null) {
         System.out.println("Result is null.");
         return;
      }
      for (RoomInfo roomInfo : roomInfos) {
          System.out.println("==============================================");
          System.out.println("Room number : " + roomInfo.getRoomNum());
          System.out.println("Room capacity : " + roomInfo.getCapacity());
          System.out.println("Rate per night : " + roomInfo.getRatePerNight());
          System.out.println("Room type : " + roomInfo.getRoomType());
          System.out.println("Web browsable : " + roomInfo.isWebBrowsable());
      }
       System.out.println("==============================================");
      
       RoomReserveInfo roomReserve = new RoomReserveInfo();
       roomReserve.setAddress("千葉県市川市なんとか町1-1-1");
       roomReserve.setName("奈々志野権兵衛");
       roomReserve.setNumOfLodgers(1);
       roomReserve.setNumOfNights(5);
       roomReserve.setRoomNum(308);
       roomReserve.setStartDate("20091225");
       roomReserve.setTelNo("123-456-7890");
      if (hotel.reserveRoom(roomReserve)) {
          System.out.println("==============================================");
          System.out.println("*********** " + roomReserve.getRoomNum() + "号室予約完了 ******************");
          System.out.println("==============================================");
      }
      else {
          System.out.println("==============================================");
          System.out.println("*********** " + roomReserve.getRoomNum() + "号室予約失敗 ******************");
          System.out.println("==============================================");
      }
      
      stayInfo.setStartDate("20091225");
      stayInfo.setNumOfLodgers(1);
      stayInfo.setNumOfNights(5);
      stayInfo.setMinRatePerNight(0);
      stayInfo.setMaxRatePerNight(50000);
      roomInfos = hotel.findRooms(stayInfo);
      if (roomInfos == null) {
         System.out.println("Result is null.");
         return;
      }
      for (RoomInfo roomInfo : roomInfos) {
          System.out.println("==============================================");
          System.out.println("Room number : " + roomInfo.getRoomNum());
          System.out.println("Room capacity : " + roomInfo.getCapacity());
          System.out.println("Rate per night : " + roomInfo.getRatePerNight());
          System.out.println("Room type : " + roomInfo.getRoomType());
          System.out.println("Web browsable : " + roomInfo.isWebBrowsable());
      }
       System.out.println("==============================================");
   }
}
--------------------------------------------------------

つまり、最初に2009年12月25日から1名5泊の予定で空き部屋(1泊0円〜50000円)を
検索(reserveRoom()メソッドを実行)したあと、

顧客住所:千葉県市川市なんとか町1-1-1
顧客氏名:奈々志野権兵衛
宿泊者数:1名
宿泊日数:5泊
宿泊する部屋番号:308号室
宿泊開始日:2009年12月25日
顧客電話番号:123-456-7890

で予約(reserveRoom()メソッドを実行)をし、再度先ほどと同じ検索をします。


このHotelClientを実行してみると、最初の検索結果では

==============================================
Room number : 308
Room capacity : 3
Rate per night : 30000
Room type : SUITE
Web browsable : false
==============================================

というように308号室もリストされ、その次に予約の結果が

==============================================
*********** 308号室予約完了 ******************
==============================================

というように完了が報告され、最後の検索結果のリストには308号室は現れませんね。

これで、308号室がちゃんと予約されたことがわかりますね。

念のため、DBViewerでBOOKINGテーブルとROOMBOOKテーブルを見てください。
BOOKINGテーブルには

"1"   "奈々志野権兵衛"   "千葉県市川市なんとか町1-1-1"   "123-456-7890"

というデータが、ROOMBOOKテーブルには、

"308"   "2009-12-25"   "1"   "1"
"308"   "2009-12-26"   "1"   "1"
"308"   "2009-12-27"   "1"   "1"
"308"   "2009-12-28"   "1"   "1"
"308"   "2009-12-29"   "1"   "1"

というデータが入っていますね。これで、予約のデータベース処理がちゃんと行われた
ことがわかります。


では、もう一度同じHotelClientを実行してみてください。
308号室はもう空いていないのだから、予約できないはずなのに
予約の結果が

==============================================
*********** 308号室予約完了 ******************
==============================================

というように、先ほどと同じく完了となってしまいます。予約前の検索結果では
308号室はリストされていないのに、予約の結果が予約完了となってしまうのは、
予約に失敗してもreserveRoom()メソッドがtrueを返しているからと考えられ
ます。

そこで、HotelSoapBindingImplのreserveRoom()メソッドのソース・コードをよく
見てみましょう。
どこにもfalseをreturnするところがありませんね。これはおかしいです。
予約に失敗したときにfalseをreturnするのを忘れていたようです。

RemoteExceptionをthrowするときは、このメソッドの実行が中断してRemoteException
をクライアントに返すのでreturnする必要はありませんが、それ以外の例外が発生した
ときは、予約失敗ということで、falseをreturnするようにソース・コードを修正し
ましょう。


というわけで、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.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Vector;

import jp.co.flsi.lecture.webservice.hotel.db.BookingDbManager;
import jp.co.flsi.lecture.webservice.hotel.db.HotelDbException;
import jp.co.flsi.lecture.webservice.hotel.db.RoomDbManager;

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;
      RoomDbManager roomDbManager = new RoomDbManager();

      try {
         String startDate = stayInfo.getStartDate();
         if (startDate.length() != 8) {
            logger.info("Invalid parameter: startDate(= " + startDate
                  + ") format not correct: must be 8 letters");
            throw new RemoteException("Invalid parameter: startDate(= " + startDate
                  + ") is invalid: must be 8 letters");
         }
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
         GregorianCalendar stayDate = new GregorianCalendar();
         stayDate.setTime(dateFormat.parse(startDate));
         if (stayDate.get(Calendar.YEAR) != Integer.parseInt(startDate.substring(0, 4)) ||
               (stayDate.get(Calendar.MONTH) + 1) != Integer.parseInt(startDate.substring(4, 6)) ||
               stayDate.get(Calendar.DAY_OF_MONTH) != Integer.parseInt(startDate.substring(6))) {
            logger.info("Invalid parameter: startDate(= " + startDate
                  + ") is not correct: impossible date");
            throw new RemoteException("Invalid parameter: startDate(= " + startDate
                  + ") is not correct: impossible date");
         }
         int numOfNights = stayInfo.getNumOfNights();
         int numOfLodgers = stayInfo.getNumOfLodgers();
         if (numOfNights < 1 || numOfLodgers < 1) {
            logger.info("Invalid parameter: numOfNights (" + numOfNights + ") or numOfLodgers ("
                  + numOfLodgers + ") is invalid.");
            throw new RemoteException("Invalid parameter: numOfNights (" + numOfNights
                  + ") or numOfLodgers (" + numOfLodgers + ") is invalid.");
         }

         roomDbManager.connect();
         roomInfoVector = roomDbManager.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 (roomDbManager.isBooked(roomNum, stayDate)) {
                     roomInfoVector.remove(roomIndex);
                     break;
                  }
                  stayDate.add(Calendar.DAY_OF_MONTH, 1);
               }
            }
         }
      } catch (HotelDbException e) {
         logger.error(e, e);
      } catch (ParseException e) {
         logger.info(e);
         throw new RemoteException("ParseException occured (startDate format not correct)", e);
      }
      catch (RemoteException e) {
         logger.info(e);
         throw e;
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         try {
            roomDbManager.disconnect();
         }
         catch (Exception e) {
            logger.info("Disconnect failed or there is no connection.");
         }
         logger.info("End ...............");
      }

      return roomInfoVector.toArray(new RoomInfo[roomInfoVector.size()]);
   }

   public boolean reserveRoom(RoomReserveInfo roomReserve) throws RemoteException {
      logger.info("Start ...............");
      BookingDbManager bookingDbManager = new BookingDbManager();

      try {
         String startDate = roomReserve.getStartDate();
         if (startDate.length() != 8) {
            logger.info("Invalid parameter: startDate(= " + startDate
                  + ") format not correct: must be 8 letters");
            throw new RemoteException("Invalid parameter: startDate(= " + startDate
                  + ") is invalid: must be 8 letters");
         }
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
         GregorianCalendar startStayDate = new GregorianCalendar();
         startStayDate.setTime(dateFormat.parse(startDate));
         if (startStayDate.get(Calendar.YEAR) != Integer.parseInt(startDate.substring(0, 4)) ||
               (startStayDate.get(Calendar.MONTH) + 1) != Integer.parseInt(startDate.substring(4, 6)) ||
               startStayDate.get(Calendar.DAY_OF_MONTH) != Integer.parseInt(startDate.substring(6))) {
            logger.info("Invalid parameter: startDate(= " + startDate
                  + ") is not correct: impossible date");
            throw new RemoteException("Invalid parameter: startDate(= " + startDate
                  + ") is not correct: impossible date");
         }
         int numOfNights = roomReserve.getNumOfNights();
         int numOfLodgers = roomReserve.getNumOfLodgers();
         if (numOfNights < 1 || numOfLodgers < 1) {
            logger.info("Invalid parameter: numOfNights (" + numOfNights + ") or numOfLodgers ("
                  + numOfLodgers + ") is invalid.");
            throw new RemoteException("Invalid parameter: numOfNights (" + numOfNights
                  + ") or numOfLodgers (" + numOfLodgers + ") is invalid.");
         }
         bookingDbManager.connect();
         bookingDbManager.insertData(roomReserve.getRoomNum(), roomReserve.getName(),
               roomReserve.getAddress(), roomReserve.getTelNo(),
               startStayDate, numOfNights, numOfLodgers);
      } catch (HotelDbException e) {
         logger.error(e, e);
         return false;
      } catch (ParseException e) {
         logger.info(e);
         throw new RemoteException("ParseException occured (startDate format not correct)", e);
      }
      catch (RemoteException e) {
         logger.info(e);
         throw e;
      }
      catch (Throwable e) {
         logger.error(e, e);
         return false;
      }
      finally {
         try {
            bookingDbManager.disconnect();
         }
         catch (Exception e) {
            logger.info("Disconnect failed or there is no connection.");
         }
         logger.info("End ...............");
      }
      return true;
   }

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

修正が終わったら、Antでデプロイしたあと、HotelClientを実行してテストして
みましょう。

今度は、ちゃんと予約の結果が

==============================================
*********** 308号室予約失敗 ******************
==============================================

というようになりますね。

念のため、Tomcatのログを見ると

jp.co.flsi.lecture.webservice.hotel.db.HotelDbException: Error: insertData() failed!

というようなエラーが出ていることがわかりますね。他にもたくさんエラー情報が
出ていますが、このログのエラー情報の見方はもうおわかりだと思うので、説明は
省略します。


もっとも、本来ならばreserveRoom()メソッドは、予約に失敗したら単にfalseを返すの
ではなく、「既に予約済み(先約がある)のため予約ができない」とか、「部屋の保守
点検期間のため予約ができない」とか、「システム・エラーのため予約ができない」と
いった失敗した理由も返して、クライアントが詳しい情報を得られるようにすべきです。
また、ログのほうも、何でもかんでもエラーとして記録するのではなく、既に先約が
ある場合など運用上問題がない場合は、エラー(error())ではなく情報(info())と
して記録すべきです。
しかしながら、今作っているのは学習のためのプログラムに過ぎないので、そこまで
細かいプログラミングは省略いたします。



ところで、予約の処理に関して、もう一つ修正すべきところがあります。

以前、トランザクション(transaction)の話をしたのを覚えていますデしょうか。
(忘れた人はvol.023を復習のこと。)
実は、JDBCでは、(自動コミットにしていない場合)コミット(commit)かロール
バック(rollback)のどちらかが実行された時点でトランザクションが完了すること
になっています。
もし、commitもrollbackもせずに接続をcloseしてしまった場合はトランザクションが
どうなるのかはJDBCでは規定されておらず、RDBMSの実装に依存します。
このような場合、MySQLはデフォルトではrollbackするのですが、RDBMSによっては
自動的にcommitしてしまうものもあるのです。

そこで、データベースの追加・更新・削除をしたときには、接続をcloseする前に
必ず明示的にcommitかrollbackのどちらかを実行することが強く推奨されています。

(なお、自動コミットになっている場合は、SQLの実行のたびに自動的にcommitされ
てトランザクションが完了する。)

ところが、BookingDbManagerのソース・コードを見てみると、例外が発生したときには
何も指定していません。


というわけで、BookingDbManagerを下記のように修正しておきましょう。

--------------------------------------------------------
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.sql.Statement;
import java.util.Calendar;
import java.util.GregorianCalendar;

import org.apache.log4j.Logger;

public class BookingDbManager extends DbManager {
   private static final String selectMaxBooknumSql = "SELECT MAX(BOOKNUM) FROM BOOKING";
   private static final String insertBookingSql = "INSERT INTO BOOKING  VALUES (?, ?, ?, ?)";
   private static final String insertRoombookSql = "INSERT INTO ROOMBOOK  VALUES (?, ?, ?, ?)";
   private static Logger logger = Logger.getLogger(BookingDbManager.class);

   public void insertData(int roomNum, String customerName, String address, String telNum,
         GregorianCalendar startStayDate, int stayNights, int lodgers) throws HotelDbException {
      logger.info("Start ...............");
      try {
         Statement selectSt = conn.createStatement();
         PreparedStatement insertBookingPs = conn.prepareStatement(insertBookingSql);
         PreparedStatement insertRoombookPs = conn.prepareStatement(insertRoombookSql);
         ResultSet rs = selectSt.executeQuery(selectMaxBooknumSql);
         int newBookingNumber = 1;
         if (rs.next()) newBookingNumber = rs.getInt(1) + 1;
         insertBookingPs.setInt(1, newBookingNumber);
         insertBookingPs.setString(2, customerName);
         insertBookingPs.setString(3, address);
         insertBookingPs.setString(4, telNum);
         insertBookingPs.executeUpdate();
         GregorianCalendar stayDate = (GregorianCalendar) startStayDate.clone();
         for (int i = 0; i < stayNights; i++) {
            insertRoombookPs.setInt(1, roomNum);
            insertRoombookPs.setDate(2, new Date(stayDate.getTimeInMillis()));
            insertRoombookPs.setInt(3, newBookingNumber);
            insertRoombookPs.setInt(4, lodgers);
            insertRoombookPs.executeUpdate();
            stayDate.add(Calendar.DAY_OF_MONTH, 1);
         }
         selectSt.close();
         insertBookingPs.close();
         insertRoombookPs.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 HotelDbException("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);
      }
      finally {
         logger.info("End ...............");
      }
   }
  
}
--------------------------------------------------------

修正が終わったら、再度Antでデプロイして下さい。


また、HotelClientで予約する部屋や日程を、まだ予約されていないものに変更
するために、HotelClientを下記のように書き換えてみましょう。

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

import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;


public class HotelClient {
   public static void main(String[] args) throws RemoteException, ServiceException {
      HotelService locator = new HotelServiceLocator();
     
      Hotel hotel = null;
      String url = "http://localhost:8080/axis/services/Hotel";
      try {
         hotel = locator.getHotel(new URL(url));
      } catch (MalformedURLException e) {
         e.printStackTrace();
      }
      StayInfoInput stayInfo = new StayInfoInput();
      stayInfo.setStartDate("20091130");
      stayInfo.setNumOfLodgers(2);
      stayInfo.setNumOfNights(3);
      stayInfo.setMinRatePerNight(0);
      stayInfo.setMaxRatePerNight(50000);
      RoomInfo[] roomInfos = hotel.findRooms(stayInfo);
      if (roomInfos == null) {
         System.out.println("Result is null.");
         return;
      }
      for (RoomInfo roomInfo : roomInfos) {
          System.out.println("==============================================");
          System.out.println("Room number : " + roomInfo.getRoomNum());
          System.out.println("Room capacity : " + roomInfo.getCapacity());
          System.out.println("Rate per night : " + roomInfo.getRatePerNight());
          System.out.println("Room type : " + roomInfo.getRoomType());
          System.out.println("Web browsable : " + roomInfo.isWebBrowsable());
      }
       System.out.println("==============================================");
      
       RoomReserveInfo roomReserve = new RoomReserveInfo();
       roomReserve.setAddress("東京都中央区なんとか町1-1-1");
       roomReserve.setName("何途可感杜香");
       roomReserve.setNumOfLodgers(2);
       roomReserve.setNumOfNights(3);
       roomReserve.setRoomNum(307);
       roomReserve.setStartDate("20091130");
       roomReserve.setTelNo("123-098-7654");
      if (hotel.reserveRoom(roomReserve)) {
          System.out.println("==============================================");
          System.out.println("*********** " + roomReserve.getRoomNum() + "号室予約完了 ******************");
          System.out.println("==============================================");
      }
      else {
          System.out.println("==============================================");
          System.out.println("*********** " + roomReserve.getRoomNum() + "号室予約失敗 ******************");
          System.out.println("==============================================");
      }
      
      stayInfo.setStartDate("20091130");
      stayInfo.setNumOfLodgers(2);
      stayInfo.setNumOfNights(3);
      stayInfo.setMinRatePerNight(0);
      stayInfo.setMaxRatePerNight(50000);
      roomInfos = hotel.findRooms(stayInfo);
      if (roomInfos == null) {
         System.out.println("Result is null.");
         return;
      }
      for (RoomInfo roomInfo : roomInfos) {
          System.out.println("==============================================");
          System.out.println("Room number : " + roomInfo.getRoomNum());
          System.out.println("Room capacity : " + roomInfo.getCapacity());
          System.out.println("Rate per night : " + roomInfo.getRatePerNight());
          System.out.println("Room type : " + roomInfo.getRoomType());
          System.out.println("Web browsable : " + roomInfo.isWebBrowsable());
      }
       System.out.println("==============================================");
   }
}
--------------------------------------------------------

そして、HotelClientを実行してテストしてみましょう。

すると、

==============================================
*********** 307号室予約完了 ******************
==============================================

というように予約が完了しているはずなのに、その後の検索の結果を見ると、

==============================================
Room number : 307
Room capacity : 3
Rate per night : 30000
Room type : SUITE
Web browsable : true
==============================================

というように307号室が空いているかのような結果がリストされています。
いったい何が悪いのでしょうか。
念のために、DBViewerでテーブルの中身を見てみても、予約のデータベース処理
がちゃんと行わていることが分かります。


これは、デバッグのための情報をログに書き出させるようにしてテストし直せば、
すぐに問題の場所を見つけ出すことができるのですが、それは次回説明致します。
今回は、読者への課題としておきますので、各自デバッグのための情報をログに
書き出させて調べてみて下さい。


(次回に続く)


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



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