ひきこもりプログラマ

C++のこととか。

SOAP::WSDL 1.25

2007-11-27 | Program
最新リリースのSOAP::WSDLを試しましたがやはりダメ。688行目で「デフォルト名前空間に属する型は単純型」と決め付けられているのが原因です。だああ! なお,ある名前空間がデフォルト名前空間でかつ接頭辞とも関連付けられているというのは合法のようです。

開発中のSOAP::WSDL 2.00は依存パッケージが多いのであんまり入れたくないのですが…どうしようかなあ。

SOAP::WSDL 1.20 その2

2007-11-22 | Software
SOAP::WSDL 1.20の導入をあきらめた真の原因をようやく思い出しましたとういか再現したので書いておきます。

SOAP::WSDL::encode() 中では以下のような処理で特定のtype定義をWSDLから検索します。
my $defaultNS=$self->{_WSDL}->{tns};
my $name=$p->{ type };
$name=~s/^$defaultNS://;

$name=~s/^(.+?:)?//;
my $path;
{ 
	$path='/'.$self->_wsdl_wsdlns.'definitions/'
		.$self->_wsdl_wsdlns."types/$schemaNS:schema/"
		."$schemaNS:complexType[@name='$name']"
		.'|'
		.'/'.$self->_wsdl_wsdlns.'definitions/'
		.$self->_wsdl_wsdlns
		."types/schema[@xmlns='"
		. $nsHash{$schemaNS.':'} 
		."' and @targetNameSpace = '"
		. $nsHash{$1}."' ]/"
		."complexType[@name='$name']"
		;
};
$complexType=$self->{_WSDL}->{xpath}->find($path)->shift 
		|| die "Error processing WSDL: $path not found";

$defaultNS は 'impl|intf' です。今回,$nameが 'impl:ArrayOf_ns1_SomeType' となっている場合にうまくいきませんでした。2回の置換を経たこの値は ':ArrayOf_ns1_SomeType' となってしまうのです。先頭に残ったコロンが余計なわけですね。

さいわい今回のWSDLではたまたまintf接頭辞は使われていないので,wsdlinit()のあとで $soap->_wsdl_tns('(impl|intf)'); を呼び出して強制的に$defaultNSを変更し,置換がうまくいくようにします。

これでOKかと思いきやさらに問題が。

SOAP::WSDL::encodeComplexType()においてWSLDからelement要素を取得するという以下の処理があります。
my $schemaNS=$self->_wsdl_schemans ? $self->_wsdl_schemans .':' : '';
my $path='.//'.$schemaNS.'element';
my $elements=$complexType->find($path) 
		|| die "Error processing WSDL: './/".$schemaNS."element' not found";

$schemaNS は'xsd:'で,$complexTypeはcomplexType要素をあらわしています。すると$pathは'.//xsd:element'となります。普通はこれでelement要素が見つかりそうなものなのですが,今回のWSDLでは,
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions (中略) xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <wsdl:types>
  <schema targetNamespace="MyService" xmlns="http://www.w3.org/2001/XMLSchema">
   <complexType name="MyComplexType">
    <sequence>
     <element name=(以下略)

というようにXML Schemaの名前空間に接頭辞を割り当てたり割り当てなかったりしており,おそらくはこれが原因でelement要素の検索に失敗しています。XPath的にこの挙動が正しいのかどうかちょっとよくわかりませんが,なんにしてもこれはゲームオーバーっぽいです(いちおう$schemaNSが空文字列になるような設定も試してみましたがxsd:に依存している処理でエラーになりました。そらそうだ)。

なんてこったー!

SOAP::WSDL 1.20

2007-11-20 | Software
3日経ったらなにがいけなかったのか忘れてしまいました。今度はちゃんとメモしておきます。

1. proxyをちゃんと設定する

最新のドキュメントに書いてなかったので忘れていました。SOAP::WSDLはSOAP::Liteを継承するのでSOAP::Lite同様endpointの指定が必要です。
my $soap = SOAP::WSDL->new( wsdl => 'http://my.host.name/MyService?wsdl')->proxy('http://my.host.name/MyService');

2. ポート名を設定する

callでoperationを呼び出すときは,input/outputのmodeにあわせてmessageが選択されます。コードは次のようになっています。
	my $portName=$self->_wsdl_portname || die "Error processing WSDL: no port name";
	
	$path='/' . $self->_wsdl_wsdlns . 'definitions/'
		.$self->_wsdl_wsdlns . "portType[\@name='$portName']/"
		.$self->_wsdl_wsdlns . "operation[\@name='$method']/"
		.$self->_wsdl_wsdlns ."$mode/\@message";

	# try cache first if caching is enabled
	my $messageName=$self->{_WSDL}->{ caching } ? $self->{_WSDL}->{ cache }->{ $path } : undef;

	$messageName=$xpath->findvalue($path);

messageを示すXPathには$portNameが含まれているので(/definitions/portType[@name='ポート名']/operation[@name='メソッド名']/モード/),この値をあらかじめ決めておく必要があります。ポート名はwsdlinitメソッド中で以下のように決定されます。
	# find address in service ports
	my $path=$self->servicename ? "[\@name='". $self->servicename ."']" : '';

	$path="/".$self->_wsdl_wsdlns."definitions/"
		.$self->_wsdl_wsdlns."service$path/"
		.$self->_wsdl_wsdlns."port/"
		.$self->_wsdl_ns->{'http://schemas.xmlsoap.org/wsdl/soap/'} 
		."address[\@location='$location']";

	my $service=$xpath->find($path)
		# get 1st matching node -> get parent node -> get value of "name"
		->shift || die "Error processing WSDL file - no such service ($path)";
	my $portName=$service->getParentNode->findvalue('@name') 
			|| die "Error processing WSDL: Cannot find ".
				"port for location $location in service $path";

つまり /definitions/service/port/@name をポート名としています。ところがこの値は前出の /definitions/portType/@name と一致する保証はありません。WSDL 1.1の仕様にも The name attribute provides a unique name among all services defined within in the enclosing WSDL document. としか書いてありません。今回利用するサービスでは見事にここでひっかかりました。最新版のSOAP::WSDLであればportname()メソッドで設定できるのですが1.20にはそのようなものはありません。しょうがないので内部メソッドを使って手動で設定します。
    $soap->wsdlinit();
    $soap->_wsdl_portname('MyPortName'); # /definitions/portType/@name の値



SOAP::WSDL

2007-11-16 | Program
めげずにSOAPプログラミング。SOAP::WSDLがイイ! と書いてあったのでppmでインストールです。ActivePerl用は1.20とのこと。ドキュメントを読むと,引数をハッシュにして渡せばWSDLに基づいて勝手にComplex TypeのSOAP::Dataを組み立ててくれるそうです。これはすごい。

テストスクリプトを書いてみました。…まともに動きません。デバッグしてみたところ,型判定のマッチ文字列が普通に間違っています。ためしに最新の1.26を入れてみます。

…SOAP::Liteが古すぎるそうです。最新版が0.70なのにActivePerl版は0.55ですからねえ。SOAP::WSDLの1.25やら1.22やらも試したが,やはりあちこち手直しが要りそう。じゃあSOAP::Liteを更新すればいいかというと,これがなんとActivePerl組み込みモジュール。

結論:ActivePerlはめどい。

なんとかしてwin32サポート手伝えんもんでしょうか。

SOAP::Data::ComplexType

2007-11-14 | Program
SOAP::Liteの作者さんも言っておられるように,SOAP::DataでComplex Typeを定義するのはけっこうめんどくさいです。ということでぐぐるさんが紹介してくれたSOAP::Data::ComplexTypeを試してみたのですが,残念ながらActivePerlでは最新版の0.044がunavailableで,as_soap_data_instanceメソッドが使えません。本当なら
$soap->method($complex->as_soap_data_instance(name=>'MyName'));

とやりたいのに,
my $prefix = 'ns';
$soap->method(
	SOAP::Data->uri($complex->OBJ_URI)
	->prefix($prefix)
	->type($prefix.':'.$complex->OBJ_TYPE)
	->name('MyName')
	->value($complex->as_soap)
	);
とか書かないといけない。超めどい。