†soap4rではwsdlを指定することで、Webサービスのoperation呼び出しをRubyオブジェクトのメソッド呼び出しにマッピングさせることができます。SOAP::WSDLDriverFactory.new(wsdlLocation).create_rpc_driverと記述します。今回はWSDLDriverFactory.newの部分を読むことにしましょう。今回対象としたバージョンは1.5.5です。soap4rはまだまだ開発途上なのかバージョンによって結構コードが変わるようです。†ではまずSOAP::WSDLDriverFactoryです。initializeメソッドは以下のようになっています。WSDL::SOAP::MethodDefCreator.new(@wsdl)end1行目のimportメソッドが解析処理をしていると考えられるのでimportメソッドに進みましょう。WSDL::Importer.import(location)endというわけで、WSDL::Importerに丸投げ委譲しています。Rubyを知らない人のために書いておくとRubyではメソッドの最後の式がメソッドの戻り値になります。1endと書けばoneメソッドの戻り値は1になります。†では、WSDL::Importer.importです。originalroot)endRubyを知らない人のためにその2ですがRubyではクラスメソッドは上記のようにself.メソッド名で定義します*1。本題に戻って、WSDL::Importerオブジェクトを作成し、インスタンスメソッドのimportメソッドを呼び出しています。WSDL::Importer自身にinitializeメソッド、importメソッドはないのでスーパークラスのWSDL::XMLSchema::Importerに移りましょう。†まず、initializeメソッド。@web_clientをnilにしています。nilend次に、importメソッド。contentendlocationがURIオブジェクトでないならURIオブジェクトにしています。URI(location));}みたいにオーバーロードを用意することになることでしょう。読解に戻って、locationで指定されたwsdlをfetchし、それを引数にしてparseメソッドを呼び出しています。fecthメソッドを見てみましょう。fetchメソッドではlocationがローカルファイルシステム上にあるかどうかで処理が分かれています。ローカルファイルシステム上にある場合は見たままなのでそれ以外の場合(httpの場合)についてコードを追いかけてみましょう。client.get_content(location)web_clientはwsdlをhttp経由で取得するためのクラスを返すメソッドです。具体的には以下のようになっています。@web_clientendまず、http-access2のロードおよびバージョンの確認を行っています。http-access2はRubyに標準で含まれていないため、ロードできなかった場合は独自のクラスを返しています。Rubyを知らない人のためにその3、Rubyは制御構造も式なので最後に書かれているものが値として返されます。今回はSOAP::NetHttpClientが返るものとします。fetchメソッドに戻って次の行、環境変数からプロキシを設定している雰囲気です。SOAP::Envがいつの間に設定されたかを確認すると、soap/wsdlDriver→soap/rpc/driver→soap/soapと読み込まれ、soap/soap.rbで定義されています。次のSOAP::Property.loadproperty(SOAP::PropertyName)ではプロパティファイル(soap/property)を読み込んでオプションをセットしています。プロパティファイルはないのでオプションはセットされませんがなかなか興味深いコードなので眺めてみましょう。†さて、SOAP::Property.loadpropertyメソッドですが、SOAP::Propertyオブジェクトをnewし、同名のインスタンスメソッドを呼び出しています。インスタンスメソッドの方のloadpropertyメソッドはこんな感じ。nilendカレント以下にsoap/propertyファイルがあればそれを、なければライブラリ検索パス($:にはライブラリ検索パスの配列が格納されています)からsoap/propertyを読み込んでいます。ファイルが見つかった場合、loadpropertyfileメソッドでファイルを開いた後、loadメソッドでプロパティを読み込んでいます。valueなので、次の[]=メソッドが呼ばれます。"ccc"]のような配列に変換されるようです。以後、nameとして"aaa.bbb.ccc"が指定されているとして読解を進めます。'.'name.split(/\./)じゃ駄目なのでしょうか?さて、[]=メソッドに戻って続きを見るとassignメソッドが呼ばれています。assignメソッドはこんな感じ。"bbb"が順にnameにセットされブロックが実行されます。to_keyメソッドは単純にnameを小文字化しているだけのようです。次のref.local_hookメソッドは今までの流れの中でフックが設定されるコードはなかったので無視することにしましょう。いうわけで、deref_keyメソッドを眺めます。refend@storeはHashでkeyとvalueの対応が登録されているようです。初回なのでself.class.newの方、つまり、新しいSOAP::Propertyオブジェクトが作られます。SOAP::Property("bbb")みたいな階層構造が構築され、ループが終了した時点でrefにSOAP::Property("bbb")が設定されます。@storeへの格納はいつの間に行われたんだ?と思ったら、||=なのでオブジェクトの生成と同時に格納されるのですね:-)さて、assignメソッドに戻ると配列の最後"ccc"についてlocal_assignメソッドが呼び出されています。valueが設定されます。[]=メソッドに戻ると登録されているフックが呼び出されるようですがフックは登録されていないはずなので無視しましょう。続いてプロパティのゲットを見てみましょう。[]メソッドはこんな感じです。referent(name_to_a(name))endで、referentメソッド。次の要素を引数に順次ブロックを実行します。最後のブロックの実行結果を返します。SOAP::Property("bbb")ということで、SOAP::Property("bbb")がinjectメソッドの結果になります。featureなので定義されていない場合はsoap/property.rbの最後で定義されています。Enumerable.instance_methods.include?('inject')moduleにあるメソッドが含まれいるかどうかは上記のように調べるのですね。次にlocal_referentメソッドです。@store[key]endというわけで、SOAP::Property("bbb")に登録されている"ccc"の値が返されます。返される値は末端のプロパティを指定した場合はString、それ以外はSOAP::Propertyとなります。†さて、WSDL::XMLSchema::Importer#fetchメソッドに戻るとSOAP::HTTPConfigLoader.set_optionsメソッドが呼ばれています。set_optionsメソッドでは次のようにhttpクライアントオブジェクトにプロパティが設定されています。valueendというわけで、先ほど無視したフックが設定されています。設定されている以上まじめに読解しないといけないようです。add_hookメソッド。endend上記の例では"proxy"という値が渡されるのでassign_hookの方が呼ばれます。assign_hookでは例のinjectメソッドにより階層構造がたどられ(今回は"proxy"だけなのでたどられません)、local_assign_hookメソッドが呼ばれます。cascade]endというわけで"proxy"にフックが登録されます。フックがいつ呼ばれるかを確認するためにもう一度assignメソッドを眺めます。true)endというわけで、local_hookメソッドが階層構造の途中の要素ではfalseで、末端ではtrueで呼ばれています。local_hookメソッド。hooksendというわけで階層の途中ではcascade=trueと指定されている場合(階層以下のいずれかのプロパティが変更されたときにフックを呼びたいという場合にtrueにすればよさそうです)、末端の場合は常にフックが追加されることになります。これらのフックは[]=メソッドで呼ばれるのでプロパティが変わったときに新しい値を設定するという使い方ができるようです。†ではそろそろメインの処理に戻りましょう。get_contentメソッドでlocationからwsdlを取得しているようです。get_contentメソッドでは、以下の動作をしています。startメソッドをブロック付きで呼び出すcreate_connectionメソッドを呼び出すプロキシやSSLを考慮してNet::HTTPオブジェクトを生成httpセッションを開始し、渡されたブロックを呼び出す。ブロックの戻り値を返す渡されたurlをget。†ようやくWSDLが取得できました。次に、WSDL::XMLSchema::Importer#importメソッドでは次にparseメソッドが呼ばれるのですが、parseメソッドはWSDL::Importerでオーバーライドされているのでそちらを見てみましょう。endendというわけでWSDL::Parserに処理が移っています。originalrootの初期値はnilです。initializeメソッドです。opt[:originalroot]endで、parseメソッド。@lastnodeendinitializeメソッドでselfを渡しているのでXSD::XMLParser#do_parseメソッドが呼ばれて解析が開始されるとWSDL::Parserにコールバックされると考えられます。end_elementとSAXっぽいメソッドが見つかります。XSD::XMLParserを見てみることにしましょう。†create_parserメソッドは次の通りです。opt)endう〜ん、何でこんな構造になっているのでしょう?ともかくXSD::XMLParser::Parser.create_parserメソッド。opt)end@@parser_factoryは?と上を見るとnilになっています。このままではnilにnewすることになってしまいます。いつの間に@@parser_factoryが定義されるのかはxsd/xmlparser.rbに戻って下の方を見るとわかります。endendというわけで複数のparser候補を順にrequireしてみて使えるparserを使っているという感じです。上2つはロードできなかったとして、xsd/xmlparser/rexmlparser.rbを見てみましょう。add_factory(self)endadd_factoryメソッド。factoryendというわけでxsd/xmlparser/rexmlparser.rbが読み込まれると@@parser_factoryが設定されるようです。以上のことからdo_parseメソッドが呼ばれるとREXMLParser#do_parseメソッドが呼ばれることになります。do_parseメソッド。self)endというわけでREXMLのStreamParserを使って解析を行っています。textの各メソッドがコールバックとして呼ばれるようです。charactersメソッド。@host.end_element(name)endこれで、WSDL::Parserに制御が戻ってきました。†さて、というわけでWSDLの各タグが見つかるとWSDL::Parser#start_elementメソッドが呼ばれるので見てみることにしましょう。node)enddecode_tagメソッドで渡されたタグに対するノードを作成し、スタックに積んでいるようです。次にdecode_tagメソッドに進みましょう。長いのでいろいろと省略します。oendまず前半部分。nilなのでDefinitions.parse_elementsメソッドが呼ばれます。Definitions.parse_elementメソッドでは単純にDefinitionsオブジェクトを生成して返しています。次に、各属性について今生成したDefinitionsオブジェクトのparse_attrメソッドが呼ばれ、属性がDefinitionsオブジェクトに設定されます。decode_tagメソッドから戻るとDefinitionsオブジェクトがスタックに積まれます。次にstart_elementメソッドが呼ばれるとスタック上にあるDefinitionsオブジェクトに対してparse_elementインスタンスメソッドが呼ばれ、definitionsタグの下にあるべきタグに対応するオブジェクトが生成されます。この処理が繰り返されることによってWSDLに対応するRubyオブジェクトツリーが構築されることになります。†ツリーの構築は大体タグに対応するRubyオブジェクトを生成し属性をしているわけですがその中で興味深いものにWSDL::Importがあります。importタグでは別のwsdlを取り込むことが可能でそれを実現するコードは以下のようになっています。endendまず、相対パスなら絶対パスに変換しています。次にすでにimport済みか確認して無駄に取得しないようにしています。取得していない場合はimportメソッドを呼び出して取得した後、キャッシュしています。次に、importメソッドです。root)endというわけで、WSDL::Importerを用いて再帰的に解析が行われています。今回はsoap4rのうち、wsdl解析処理を読みました。今回学んだこととしては以下があります。利用可能なライブラリの検出、ラッピング方法メソッドが定義されているかの確認方法Ruby的なコードの読み方:-)それではみなさんもよいコードリーディングを。正確に言うとクラスメソッドはクラスオブジェクトの特異メソッドとして定義されます。