wsdl 1.1 サポート
ドットコムブームのさなか猫も杓子も WSDL で記述されたSOAP サービスの一つや二つは書いていたのではないか。この WSDL ドキュメントを詳しくみてみると、実はメッセージのレイアウトを記述した XML Schema ドキュメントが埋めこまれており、それが結構な部分を占めいていることが分かる。残りは些細なものだ。scalaxb は XML Schema 部分は処理できるため、WSDL をサポートし始めるのは、時間の問題だったと言えるだろう。
使用例
- wsdl ドキュメントをローカル環境にダウンロードする。
- sbt-scalaxb を使っている場合はそれを
src/main/wsdl
に置く。
サンプルの設定例はこんな感じ:
import ScalaxbKeys._ val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.2" val scalaParser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1" val dispatchV = "0.11.1" // change this to appropriate dispatch version val dispatch = "net.databinder.dispatch" %% "dispatch-core" % dispatchV organization := "com.example" name := "scalaxb-stockquote-sample" scalaVersion := "2.11.1" scalaxbSettings packageName in (Compile, scalaxb) := "stockquote" dispatchVersion in (Compile, scalaxb) := dispatchV async in (Compile, scalaxb) := true sourceGenerators in Compile <+= scalaxb in Compile libraryDependencies ++= Seq(scalaXml, scalaParser, dispatch)
以下の9ファイルが生成される:
- scalaxb/httpclients_async.scala
- scalaxb/httpclients_dispatch_async.scala
- scalaxb/scalaxb.scala
- scalaxb/soap12_async.scala
- soapenvelope12/soapenvelope12.scala
- soapenvelope12/soapenvelope12_xmlprotocol.scala
- stockquote/stockquote.scala
- stockquote/stockquote_type1.scala
- stockquote/xmlprotocol.scala
コード例:
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent._ import scala.concurrent.duration._ val service = (new stockquote.StockQuoteSoap12Bindings with scalaxb.SoapClientsAsync with scalaxb.DispatchHttpClientsAsync {}).service val fresponse = service.getQuote(Some("GOOG")) val response = Await.result(fresponse, 5 seconds) println(response)
なにそれ?
その前に少し断っておきたいのは、ここではCake パターンが使われているので、何かよく分からない場合はリンク先を読んでほしい。
以下で実際に生成されたコードをみていこう。まずは、stockquote.scala:
// Generated by <a href="http://scalaxb.org/">scalaxb</a>. package stockquote import scala.concurrent.Future trait StockQuoteSoap { def getQuote(symbol: Option[String]): Future[stockquote.GetQuoteResponse] }
これは web サービスのインターフェイス部分を定義し、XML や SOAP といった実装から抽象化されている。次。
長いので、xmlprotocol.scala を全引用はしないが、XML の case class へのパーシング、および wsdl の場合は以下のような SOAP 1.2 バインディングを実装する:
trait StockQuoteSoap12Bindings { this: scalaxb.SoapClientsAsync => lazy val targetNamespace: Option[String] = Some("http://www.webserviceX.NET/") lazy val service: stockquote.StockQuoteSoap = new StockQuoteSoap12Binding {} def baseAddress = new java.net.URI("http://www.webservicex.net/stockquote.asmx") trait StockQuoteSoap12Binding extends stockquote.StockQuoteSoap { import scalaxb.ElemName._ def getQuote(symbol: Option[String]): Future[stockquote.GetQuoteResponse] = soapClient.requestResponse(scalaxb.toXML(stockquote.GetQuote(symbol), Some("http://www.webserviceX.NET/"), "GetQuote", defaultScope), Nil, defaultScope, baseAddress, "POST", Some(new java.net.URI("http://www.webserviceX.NET/GetQuote"))).transform({ case (header, body) => scalaxb.fromXML[stockquote.GetQuoteResponse]((body.headOption getOrElse {body}), Nil) }, { case x: scalaxb.Fault[_] => x case x => x }) } }
Cake パターンでは、StockQuoteSoap12Bindings
はケーキの一切れのようなモジュールを表す。これは、soap12_async.scala 中のもう一つのモジュール scalaxb.SoapClientsAsync
に依存していると宣言している。
trait SoapClientsAsync { this: HttpClientsAsync => lazy val soapClient: SoapClientAsync = new SoapClientAsync {} def baseAddress: java.net.URI trait SoapClientAsync { implicit lazy val executionContext = scala.concurrent.ExecutionContext.Implicits.global import soapenvelope12.{Fault => _, _} val SOAP_ENVELOPE_URI = "http://www.w3.org/2003/05/soap-envelope" def requestResponse(body: scala.xml.NodeSeq, headers: scala.xml.NodeSeq, scope: scala.xml.NamespaceBinding, address: java.net.URI, webMethod: String, action: Option[java.net.URI]): Future[(scala.xml.NodeSeq, scala.xml.NodeSeq)] = { val bodyRecords = body.toSeq map {DataRecord(None, None, _)} val headerOption = headers.toSeq.headOption map { _ => Header(headers.toSeq map {DataRecord(None, None, _)}, Map()) } val envelope = Envelope(headerOption, Body(bodyRecords, Map()), Map()) buildResponse(soapRequest(Some(envelope), scope, address, webMethod, action)) } .... } }
これは、scalaxb の非同期な SOAP の実装で、内部では、scalaxb が生成した SOAP エンベロープ case class (soapenvelope12.scala と soapenvelope12_xmlprotocol.scala に定義されている)を使っている。しかし、大切なのは、scalaxb.SoapClientsAsync
モジュールが 以下のscalaxb.HttpClientsAsync
に依存していることだ:
package scalaxb import concurrent.Future trait HttpClientsAsync { def httpClient: HttpClient trait HttpClient { def request(in: String, address: java.net.URI, headers: Map[String, String]): Future[String] } }
これは httpClient
を実装していないので、抽象 trait となる。最後に、httpclients_dispatch_async.scala。
DispatchHttpClientsAsync
scalaxb.DispatchHttpClientsAsync
は上記の scalaxb.HttpClientsAsync
モジュールのレファレンス実装だ。
package scalaxb import concurrent.Future trait DispatchHttpClientsAsync extends HttpClientsAsync { lazy val httpClient = new DispatchHttpClient {} trait DispatchHttpClient extends HttpClient { import dispatch._, Defaults._ val http = new Http() def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = { val req = url(address.toString).setBodyEncoding("UTF-8") <:< headers << in http(req > as.String) } } }
scalaxb は Dispatch 0.8、 0.10.x、および 0.11.x をサポートする。Dispatch のバージョンによって生成されるコードが微妙に違うため、dispatchVersion in (Compile, scalaxb)
を設定する必要がある。現在のデフォルトは 0.11.1 だけどもこれは将来的に変更される。
val dispatchV = "0.11.1" // change this to appropriate dispatch version val dispatch = "net.databinder.dispatch" %% "dispatch-core" % dispatchV dispatchVersion in (Compile, scalaxb) := dispatchV async in (Compile, scalaxb) := true // set to false for older version of dispatch libraryDependencies ++= Seq(dispatch, ....)
ケーキを一切れ、二切れ
まとめよう。
- 生成された
stockquote.StockQuoteSoap12Bindings
モジュールは ... scalaxb.SoapClientsAsync
モジュールに依存し、それは ...scalaxb.HttpClientsAsync
モジュールの一実装に依存する (DispatchHttpClientsAsync
を使う)
なんで、そんなヤヤコシイ事?答はモジュール性にある。例えるなら、ブレーキシステムが故障することを恐れずにカーステをアップグレードできるというようなことだ。何らかの理由で独自の http 処理がしたい?独自の HttpClientsAsync
モジュールを書けばいい。パスワードを SOAP ヘッダに入れたい?scalaxb.SoapClientsAsync
を拡張すればいい。
困ったら
何か質問や意見があれば英語のメーリングリストか、日本語で@scalaxb にツイートすると答えるかも。