■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2009年05月10日

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

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


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


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


さて、これまで正規表現を使って日付の形式(YYYYMMDD)をチェックしていましたが、
実は、vol.078でお話したSimpleDateFormatクラスを使用すると、日付の形式のチェック
やGregorianCalendarへの変換がもっと簡単になります。

たとえば、

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");

というふうにして"yyyyMMdd"形式のSimpleDateFormatオブジェクトを生成しておくと、
そのparse()メソッドを使って、

java.util.Date parsedStartDate = dateFormat.parse("20090530");

のようにして、YYYYMMDD形式の日付の文字列をDateオブジェクトに変換してくれます。
このとき、引数に指定されたデータがDateオブジェクトに変換できない場合、すなわち、
引数の値が日付の形式として解釈できない場合には、ParseExceptionという例外が投げ
られます。(これでも日付の形式のチェックとしてはまだ完璧ではないのだが。)


というわけで、今回はSimpleDateFormatクラスを使ってHotelSoapBindingImplクラスを
少し書き換えてみましょう。

このとき、ParseExceptionという例外が発生するのは、クライアント側の引数の指定の
仕方が悪かったのが原因であるから、ParseExceptionを捉えたら、それを
RemoteExceptionに変換してクライアント側に投げるようにしましょう。

また、java.util.Dataオブジェクトは、(GregorianCalendarのスーパークラスである)
CalendarのsetTime()というメソッドを使ってGregorianCalendarに変換できるので、
これを使えばGregorianCalendarへの変換がかなり簡単になります。

というわけで、HotelSoapBindingImplを下記のように書き換えましょう。
(HotelSoapBindingImplはJStudySOAPプロジェクトの中の
jp.co.flsi.lecture.webservice.hotelパッケージの中にありましたね。)

--------------------------------------------------------
/**
 * 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 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();
         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));
         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.");
         }

         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);
      } catch (ParseException e) {
         logger.info(e);
         throw new RemoteException("ParseException occured (startDate format not correct)", e);
      }
      finally {
         try {
            DbManager.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 ...............");
      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));
         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);
      } catch (ParseException e) {
         logger.info(e);
         throw new RemoteException("ParseException occured (startDate format not correct)", e);
      }
      finally {
         try {
            DbManager.disconnect();
         }
         catch (Exception e) {
            logger.info("Disconnect failed or there is no connection.");
         }
         logger.info("End ...............");
      }
      return true;
   }

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

日付の処理がずいぶん簡単になりましたね。ただし今回は、引数が規定違反のときに
RemoteExceptionをクライアントに投げるようにしているので、その分コードが多く
見えるところもあります。

また、上記のソース・コードでは、finallyブロックが

      finally {
         try {
            DbManager.disconnect();
         }
         catch (Exception e) {
            logger.info("Disconnect failed or there is no connection.");
         }
         logger.info("End ...............");
      }

のように、ちょっと複雑になっていますが、これは、引数の規定違反でRemoteException
が投げれらたときなどに

DbManager.connect();

を実行せずにfinallyブロックを実行することになるため、

DbManager.disconnect();

で例外を発生してしまうことを想定して、その例外を捕捉しておくようにしたものです。
そもそもデータベースに接続されていないので、disconnect()しなくていい場面なので
この例外は無視していいものですが、こうやって捕捉しておかないと、メソッドの外
に例外が投げられてしまって、誰かを困惑させてしまう恐れがあるのです。
(上のログに書き込んでいるメッセージの文も誰かを困惑させる恐れがあるかも知れま
せんが、そこらへんは実際にプログラムを作る人が文を工夫してください。なお、一応
infoにしていますので、errorではないということをログを見る人が意識していれば
問題ないことは分かるでしょう。)



では、このソース・コードの変更および保管が終わったら、例によって、Antでデプロイを
行っておきましょう。
デプロイのやり方を忘れた人のために、やり方を下記に再度提示しておきましょう。

まず、Tomcatを起動しておき、

(1) JStudySOAP配下のbuild.xmlを右クリックし、「実行」→「3 Antビルド...」を
選択します。

(2) deployにチェック・マークを入れ、他のターゲットはチェック・マークを外した
状態で、「実行」ボタンをクリックします。

(3) Antが実行され、コンソールにメッセージが表示されます。最終的に

BUILD SUCCESSFUL
Total time: 5 seconds

のようなメッセージが表示されればデプロイが完了です。
(ただし、Total timeの数字は環境によって異なります。)



では、上記のHotelSoapBindingImplの変更に対するテストができるように、
HotelClientのほうも変更してみましょう。
(HotelClientはJStudySoapClientプロジェクトの中の
jp.co.flsi.lecture.webservice.hotelパッケージの中にありましたね。)

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("2009ABCD");
      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("==============================================");
   }
}
--------------------------------------------------------

変更および保管が終わったら、HotelClientを実行してみて下さい。

コンソールに

 faultString: java.rmi.RemoteException: ParseException occured (startDate format not correct); nested exception is:
java.text.ParseException: Unparseable date: &quot;2009ABCD&quot;

というような例外のメッセージが表示されているでしょう。このうち、
ParseException occured (startDate format not correct)
の部分は、先ほどソース・コードの中に

throw new RemoteException("ParseException occured (startDate format not correct)", e);

という文で文字列として書き込んでいた部分であることが分かりますね。一方、
nested exception is:の以下の部分は、RemoteExceptionのコンストラクターの最後の引数
に指定したeから取り出されたものです。上のようにコンストラクターに指定すると、eの
getCause()メソッドが呼び出されて、eという例外の原因が取り出されます。

Unparseable date: &quot;2009ABCD&quot;

というのは分かりにくいですが、「2009ABCDというデータは日付として解釈できない」
という意味です。

つまり、HotelClientの中で

stayInfo.setStartDate("2009ABCD");

というように設定を行ったのがいけないことが分かります。


では、今度は、この"2009ABCD"の部分を"20091225"に書き換えてみましょう。
つまり、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("==============================================");
   }
}
--------------------------------------------------------

変更および保管が終わったら、HotelClientを実行してみて下さい。

今度は日付を正しく指定したのだからうまくいくと思いきや、コンソールに

 faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
 faultSubcode:
 faultString: java.lang.NullPointerException

というような例外が報告されますね。


実は、これ、予め計画的に、エラーが起こるようにWebサービスのプログラム(Axisの
アプリケーション)を細工しておいたのです。
エラーがどこで発生しているか分かりますでしょうか?
Tomcatのログを見ても分かりませんね。
実は、ログの取り方が不十分なので、こういうエラーが出ても原因を調べるのに苦労
してしまうんですね。
どんな例外も逃さず記録できるようにログを取る必要があります。

というわけで、ログの取り方を修正しましょう。


(次回に続く)


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



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