以前に RailGo Webサービス と Apache Axis2 を試す というのをやったけど必要なライブラリJARファイルが多すぎて(51個!)イヤになったので、今度は JAX-WS のリファレンス実装でやってみる。

RailGoのAPI は RailGoアプリケーションの手引きExpService06 Web サービス あたりを参照。

JAX-WS の wsimport でスタブソースコードを生成

JAX-WS のリファレンス実装: jax-ws: JAX-WS Reference Implementation
今回は、バージョン 2.1.2 M1 を使用。


C:\work\jaxws-ri\bin>mkdir railgosrc
 
C:\work\jaxws-ri\bin>wsimport -extension -s railgosrc -p railgo.jaxws http:/
/wstest.railgo.jp/Expservice06.asmx?WSDL
parsing WSDL...
 
 
[WARNING] SOAP port "ExpService06Soap12": uses a non-standard SOAP 1.2 binding.
  line 604 of http://wstest.railgo.jp/Expservice06.asmx?WSDL
 
generating code...
 
 
compiling code...

-extension を使ったけどまだ警告が出てしまう。とりあえずスルーで。

スタブソースコードを利用したサンプルプログラム

ソースコードは3つ。

-RailGoSample.java
-RailGoAuthHandlerResolver.java
-RailGoAuthHandler.java

ソースコード(RailGoSample.java)


import java.util.*;
import railgo.jaxws.*;
 
public class RailGoSample {
 
  public static void main(String[] args) {
 
    // 駅すぱあと 路線検索Webサービス
    ExpService06 exps = new ExpService06();
    
    // 認証情報をセット
    Authentication auth = new Authentication();
    auth.setUser("mctuser3");
    auth.setPassword("symfyws6");
    RailGoAuthHandlerResolver authhr =
      new RailGoAuthHandlerResolver(
        exps.getHandlerResolver(), auth);
    exps.setHandlerResolver(authhr);
    
    ExpService06Soap service = exps.getExpService06Soap();
    
    // 検索パラメータをセット
    SearchStation search = new SearchStation();
    search.setStationYomi("なごや");
    search.setAreaType(AreaType.JAPAN);
    search.setStationType(StationType.RAIL_ROAD);
    search.setDate(20070707);
    String stationYomi = "なごや";
    AreaType areaType = AreaType.JAPAN;
    StationType stationType = StationType.RAIL_ROAD;
    int date = 20070707;
 
    // 検索を実行
    ArrayOfStation stations =
      service.searchStation(
        stationYomi, areaType, stationType, date);
    
    // 駅の一覧を表示
    List<Station> ss = stations.getStation();
    for (int i = 0; i < ss.size(); i++) {
      Station s = ss.get(i);
      System.out.println(s.getName());
    }
  }
 
}

ソースコード(RailGoAuthHandlerResolver.java)


import java.util.*;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;
import railgo.jaxws.*;
 
public class RailGoAuthHandlerResolver implements HandlerResolver {
  
  private HandlerResolver resolver;
  private Authentication auth;
 
  public RailGoAuthHandlerResolver(
    HandlerResolver resolver, Authentication auth) {
    this.resolver = resolver;
    this.auth = auth;
  }
 
  public List<Handler> getHandlerChain(PortInfo portInfo) {
    List<Handler> list;
    if (resolver != null) {
      list = resolver.getHandlerChain(portInfo);
    } else {
      list = new ArrayList<Handler>();
    }
    list.add(new RailGoAuthHandler(auth));
    return list;
  }
}

ソースコード(RailGoAuthHandler.java)


import java.util.Set;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.soap.Name;
import railgo.jaxws.*;
 
public class RailGoAuthHandler implements SOAPHandler<SOAPMessageContext> {
  
  private Authentication auth;
  
  public RailGoAuthHandler(Authentication auth) {
    this.auth = auth;
  }
  
  public boolean handleMessage(SOAPMessageContext context) throws RuntimeException {
    try{
      Boolean outboundProperty =
        (Boolean)context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
      if(outboundProperty.booleanValue()){
        return handleOutboundMessage(context);
      } else {
        return false;
      }
    }catch(Exception e){
      throw new RuntimeException(e);
    }
  }
  
  private boolean handleOutboundMessage(SOAPMessageContext context) throws SOAPException{
    try {
      SOAPEnvelope env = context.getMessage().getSOAPPart().getEnvelope();
      SOAPHeader header = env.getHeader();
      
      if (header == null) {
        header = env.addHeader();
      }
      
      // ローカル名
      String localName = "Authentication";
      // 名前空間の接頭辞
      String prefix = "";
      // 名前空間の URI
      String uri = "http://expart.est.co.jp/ExpService";
      
      Name name = env.createName(localName, prefix, uri);
      SOAPHeaderElement he = header.addHeaderElement(name);
      SOAPElement usr = he.addChildElement("User");
      SOAPElement passwd = he.addChildElement("Password");
      usr.setTextContent(auth.getUser());
      passwd.setTextContent(auth.getPassword());
 
      return true;
      
    } catch (SOAPException e) {
      throw e;
    }
  }
  
  public boolean handleFault(SOAPMessageContext context) {
    return true; // do nothing
  }
  
  public void close(MessageContext context) {
    return; // do nothing
  }
  
  public Set getHeaders() {
    return null;
  }
}

実行出力結果


名古屋
近鉄名古屋
名鉄名古屋
名古屋競馬場前
徳重・名古屋芸大
名古屋港
名古屋大学
ナゴヤドーム前矢田

RailGo の認証

wsimport は Authentication というクラスを生成してくれたが、これは存在の意味がない(-_-;)
HandlerResolver と SOAPHandler の実装クラスである RailGoAuthHandler と RailGoAuthHandlerResolver というクラスを自前で作って、はじめて使えるクラスになった(別に無くてもいいし)。

HandlerResolver と SOAPHandler の実装は ブログ: 岡崎 - Okazaki's blog - マッシュアップ2 : RailGoで駅検索その2jax-ws: A little bit about Handlers in JAX-WS がとても参考になった。

また、RailGoAuthHandler.java に記述している認証用のURL(http://expart.est.co.jp/ExpService)は、ExpService06 Web サービス の以下のサンプルを参考にした。


以下は SOAP 1.1 の要求および応答のサンプルです。表示されるプレースホルダを実際の値で置き換える必要があります。
 
POST /Expservice06.asmx HTTP/1.1
Host: wstest.railgo.jp
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://expart.est.co.jp/ExpService/GetDefaultNavigation"
 
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <Authentication xmlns="http://expart.est.co.jp/ExpService">
      <User>string</User>
      <Password>string</Password>
    </Authentication>
  </soap:Header>
  <soap:Body>
    <GetDefaultNavigation xmlns="http://expart.est.co.jp/ExpService" />
  </soap:Body>
</soap:Envelope>

ObjectFactory で JAXBElement を生成するパターン

よく見たら、ObjectFactory#createAuthentication で JAXBElement<Authentication> を生成できるコードが作られていた。
これを使うと、認証用の URL を自分で書いたクラスに埋め込む必要は無くなるので、Web Service の API がバージョンアップしたときにスタブコードだけを入れ替えればよくなるはず。
といっても、HandlerResolver と Handler 実装はけっきょく必要だったりするので最初の手間はそんなに変わらない。

サンプルソースコードはこんな感じ。

-RailGoSample2.java
-RailGoAuthHandlerResolver2.java
-RailGoAuthHandler2.java


import java.util.*;
import javax.xml.bind.JAXBElement;
import railgo.jaxws.*;
 
public class RailGoSample2 {
 
  public static void main(String[] args) {
 
    // 駅すぱあと 路線検索Webサービス
    ExpService06 exps = new ExpService06();
    
    // 認証情報をセット
    Authentication auth = new Authentication();
    auth.setUser("mctuser3");
    auth.setPassword("symfyws6");
    RailGoAuthHandlerResolver2 authhr =
      new RailGoAuthHandlerResolver2(
        exps.getHandlerResolver(), auth);
    exps.setHandlerResolver(authhr);
    ExpService06Soap service = exps.getExpService06Soap();
    
    // 検索パラメータをセット
    SearchStation search = new SearchStation();
    search.setStationYomi("なごや");
    search.setAreaType(AreaType.JAPAN);
    search.setStationType(StationType.RAIL_ROAD);
    search.setDate(20070707);
    String stationYomi = "なごや";
    AreaType areaType = AreaType.JAPAN;
    StationType stationType = StationType.RAIL_ROAD;
    int date = 20070707;
 
    // 検索を実行
    ArrayOfStation stations =
      service.searchStation(
        stationYomi, areaType, stationType, date);
    
    // 駅の一覧を表示
    List<Station> ss = stations.getStation();
    for (int i = 0; i < ss.size(); i++) {
      Station s = ss.get(i);
      System.out.println(s.getName());
    }
  }
 
}

import java.util.*;
 
import javax.xml.bind.JAXBElement;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;
import railgo.jaxws.*;
 
public class RailGoAuthHandlerResolver2 implements HandlerResolver {
  
  private HandlerResolver resolver;
  private Authentication auth;
 
  public RailGoAuthHandlerResolver2(
    HandlerResolver resolver, Authentication auth) {
    this.resolver = resolver;
    this.auth = auth;
  }
 
  public List<Handler> getHandlerChain(PortInfo portInfo) {
    List<Handler> list;
    if (resolver != null) {
      list = resolver.getHandlerChain(portInfo);
    } else {
      list = new ArrayList<Handler>();
    }
    // ObjectFactory 内に認証先の URL が埋め込まれている
    ObjectFactory of = new ObjectFactory();
    JAXBElement<Authentication> jauth = of.createAuthentication(auth);
    list.add(new RailGoAuthHandler2(jauth));
    return list;
  }
}

import java.util.Set;
import javax.xml.bind.JAXBElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import railgo.jaxws.*;
 
public class RailGoAuthHandler2 implements SOAPHandler<SOAPMessageContext> {
  
  private JAXBElement<Authentication> jauth;
  
  public RailGoAuthHandler2(JAXBElement<Authentication> jauth) {
    this.jauth = jauth;
  }
  
  public boolean handleMessage(SOAPMessageContext context) throws RuntimeException {
    try{
      Boolean outboundProperty =
        (Boolean)context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
      if(outboundProperty.booleanValue()){
        return handleOutboundMessage(context);
      } else {
        return false;
      }
    }catch(Exception e){
      throw new RuntimeException(e);
    }
  }
  
  private boolean handleOutboundMessage(SOAPMessageContext context) throws SOAPException{
    try {
      SOAPEnvelope env = context.getMessage().getSOAPPart().getEnvelope();
      SOAPHeader header = env.getHeader();
      
      if (header == null) {
        header = env.addHeader();
      }
      
      SOAPHeaderElement he = header.addHeaderElement(jauth.getName());
      Authentication auth = jauth.getValue();
      SOAPElement usr = he.addChildElement("User");
      SOAPElement passwd = he.addChildElement("Password");
      usr.setTextContent(auth.getUser());
      passwd.setTextContent(auth.getPassword());
 
      return true;
      
    } catch (SOAPException e) {
      throw e;
    }
  }
  
  public boolean handleFault(SOAPMessageContext context) {
    return true; // do nothing
  }
  
  public void close(MessageContext context) {
    return; // do nothing
  }
  
  public Set getHeaders() {
    return null;
  }
}

コンポーネントスクエア - 情報発信-2005年度丸山先生レクチャーシリーズ:第4回 in Fujitsu(資料ダウンロード) にある "JAXBとJAX-WS" by 稚内北星学園大学 丸山不二夫 (http://www.c-sq.com/modules/article/download.php?fileid=169) とか参考になるかも。いろいろ詳しく書いてある。Unmarshaller というのを使えばもう少しラクになる?

スタブクラス一覧

-AirlineFareType.java
-AreaType.java
-ArrayOfArrayOfStation.java
-ArrayOfCourse.java
-ArrayOfFare.java
-ArrayOfFareSection.java
-ArrayOfPassSection.java
-ArrayOfRouteSection.java
-ArrayOfStation.java
-ArrayOfStationType.java
-ArrayOfString.java
-ArrayOfSurchargeSection.java
-Authentication.java
-CareState.java
-Corporation.java
-Course.java
-ExpService06.java
-ExpService06Soap.java
-Fare.java
-FareSection.java
-GeodeticDatum.java
-GetDefaultNavigation.java
-GetDefaultNavigationResponse.java
-GetUserPoints.java
-GetUserPointsResponse.java
-Line.java
-LineType.java
-Navigation.java
-ObjectFactory.java
-package-info.java
-PassSection.java
-PassType.java
-RouteSection.java
-SearchCourse.java
-SearchCourseAll.java
-SearchCourseAllResponse.java
-SearchCourseResponse.java
-SearchStation.java
-SearchStationAll.java
-SearchStationAllResponse.java
-SearchStationResponse.java
-SeatType.java
-SortType.java
-Station.java
-StationType.java
-SurchargeSection.java
-Train.java
-UseState.java
-WaitTimeState.java

JAX-WS のライブラリJARファイル18個

-activation.jar
-FastInfoset.jar
-http.jar
-jaxb-api.jar
-jaxb-impl.jar
-jaxb-xjc.jar
-jaxws-api.jar
-jaxws-rt.jar
-jaxws-tools.jar
-jsr173_api.jar
-jsr181-api.jar
-jsr250-api.jar
-resolver.jar
-saaj-api.jar
-saaj-impl.jar
-sjsxp.jar
-stax-ex.jar
-streambuffer.jar

まぁ、Apache Axis2 よりは少ないから良しとするか……

tags: zlashdot WebServices JAXWS Java RailGo WSDL WebServices

Posted by NI-Lab. (@nilab)