diff --git a/Build.PL b/Build.PL index 9503d03..6793005 100644 --- a/Build.PL +++ b/Build.PL @@ -1,7 +1,12 @@ use Module::Build; - Module::Build->new( - dist_abstract => 'WSDL support for SOAP::Lite', - module_name => 'SOAP::WSDL', - license => 'artistic', - requires => { 'SOAP::Lite' => 0.55, 'XML::XPath' => 0 } - )->create_build_script; \ No newline at end of file +Module::Build->new( + dist_abstract => 'WSDL support for SOAP::Lite', + module_name => 'SOAP::WSDL', + license => 'artistic', + requires => { + 'SOAP::Lite' => 0., + 'XML::XPath' => 0 + }, + buildrequires => { 'Test::More' => 0 }, + +)->create_build_script; diff --git a/CHANGES b/CHANGES index 3dd3c13..65817a1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +* v1.22 2007/05/30 - auto-discover service and port again +- re-introduces auto-detecting of servicename and portname +- fixes #27325: Test fails with Test::Pod::Coverage v 1.06. +- Now build requires Test::More +- documentation update +- cosmetics + 2007/05/28 private methods made private and pod update - added pod tests - made encodeComplexType and method generators private diff --git a/META.yml b/META.yml index f924960..0e6284b 100644 --- a/META.yml +++ b/META.yml @@ -1,21 +1,18 @@ --- name: SOAP-WSDL -version: 1.21 -author: - - |- - Martin Kutter - Giovanni S. Fois giovannisfois@tiscali.it +version: 1.22 +author: [] abstract: WSDL support for SOAP::Lite license: artistic resources: license: http://opensource.org/licenses/artistic-license.php requires: - SOAP::Lite: 0.55 + SOAP::Lite: 0 XML::XPath: 0 provides: SOAP::WSDL: file: lib/SOAP/WSDL.pm - version: 1.21 + version: 1.22 generated_by: Module::Build version 0.28 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.2.html diff --git a/README b/README index b77ac1c..62407bf 100644 --- a/README +++ b/README @@ -20,13 +20,10 @@ need the following packages: INSTALLING -Use the usual mantra: - -perl Makefile.PL -make -make test -make install - +perl Build.PL +perl Build +perl Build test +perl Build install LICENSE diff --git a/lib/SOAP/WSDL.pm b/lib/SOAP/WSDL.pm index d02c598..dfb2832 100644 --- a/lib/SOAP/WSDL.pm +++ b/lib/SOAP/WSDL.pm @@ -3,709 +3,913 @@ package SOAP::WSDL; use SOAP::Lite; use vars qw($VERSION @ISA); -use XML::XPath; +use XML::XPath; use strict; use warnings; use Data::Dumper; -@ISA= qw(SOAP::Lite); +@ISA = qw(SOAP::Lite); -$VERSION = "1.21"; +$VERSION = "1.22"; -sub wsdlinit { - my $self=shift; - my %opt=@_; - $self->{_WSDL}->{ cache } = {}; - $self->{_WSDL}->{ caching } = $opt{ caching }; - $self->{_WSDL}->{ cache_directory } = $opt{ cache_directory } if exists($opt{cache_directory}); - $self->{_WSDL}->{ checkoccurs } = 1 unless defined( $self->{_WSDL}->{ checkoccurs } ); - - if (($self->{_WSDL}->{ caching }) && (! $self->{_WSDL }->{ fileCache })) { - $self->wsdl_cache_init(); - }; - - #makeup xpath document - my $xpath; - # check cache - if ($self->{_WSDL}->{ fileCache }) { - # get xpath from cache - $xpath = $self->{_WSDL}->{ fileCache }->get( $self->wsdl ); - # get in-memory cache from cache - if ($self->{_WSDL}->{ caching }) { - my $cache=$self->{_WSDL}->{ fileCache }->get( $self->wsdl."_cache" ); - $self->{_WSDL}->{ cache } = $cache || {}; - } - } - unless ($xpath) { - $xpath = XML::XPath->new( - xml => SOAP::Schema->new(schema_url => $self->wsdl)->access ); - } - - ($xpath) || - die "Error processing WSDL: Cannot create XPath object"; +sub wsdlinit +{ + my $self = shift; + my %opt = @_; + $self->{ _WSDL }->{ cache } = {}; + $self->{ _WSDL }->{ caching } = $opt{ caching }; + $self->{ _WSDL }->{ cache_directory } = $opt{ cache_directory } + if exists( $opt{ cache_directory } ); + $self->{ _WSDL }->{ checkoccurs } = 1 + unless defined( $self->{ _WSDL }->{ checkoccurs } ); - $self->_wsdl_xpath( $xpath ); - - # Get root element () and get - # default prefix (the root element's one). - my $definitions=undef; - $definitions=$xpath->find('/*[1]')->shift; - - my $prefix= $definitions->getPrefix; - $self->_wsdl_wsdlns( $prefix ? $prefix.':' : '' ); - - # get the targetNamespace - my $tns= $definitions->getAttribute('targetNamespace') - || die "Error processing WSDL: cannot get "; - - # look for schema namespace & prefix for targetNamespace - my ($defaultNS, $schemaNS) = ('',''); - my @_ns_sub_list=(); - - my $nameSpaces=$definitions->getNamespaces - || die "Error processing WSDL: cannot get namespaces"; - my $nsHash={}; - foreach my $ns( @{ $nameSpaces } ) { - $xpath->set_namespace($ns->getPrefix, $ns->getData); - if ($ns->getData eq $tns) { - push @_ns_sub_list, $ns->getPrefix; - next; - } - - #------------------------------------------------------- - # Here we look for the default wsdl namespace which is used *only* - # when we are looking for the arrays restrictions. - # Originally the prefix was used for this, but sometimes the prefix - # can be omitted - #------------------------------------------------------- - $ns->getPrefix eq "#default" and next; - if($ns->getData eq "http://schemas.xmlsoap.org/wsdl/"){ - $self->_wsdl_wsdlExplicitNS ($ns->getPrefix?$ns->getPrefix.":":""); - } + if ( ( $self->{ _WSDL }->{ caching } ) + && ( !$self->{ _WSDL }->{ fileCache } ) ) + { + $self->wsdl_cache_init(); + } - # the schema namespace is hardcoded in to the SOAP::Constants package, - # in the Lite.pm module - if (defined $SOAP::Constants::XML_SCHEMAS{$ns->getData } and - $SOAP::Constants::XML_SCHEMAS{$ns->getData }=~/SOAP::XMLSchema\d+/){ - $schemaNS=$ns->getPrefix; - } - $nsHash->{ $ns->getData } = $ns->getPrefix.':'; - } - - $self->_wsdl_ns($nsHash); - $defaultNS = join('|', @_ns_sub_list); + #makeup xpath document + my $xpath; - $self->_wsdl_tns($defaultNS); - $self->_wsdl_tns_uri($tns); - $self->_wsdl_schemans($schemaNS); - #--- - #-- TBD: remove all the hardcoded urls - $self->_wsdl_soapns($self->_wsdl_ns->{'http://schemas.xmlsoap.org/wsdl/soap/'}); - - #the default namespaces for types - $self->{_WSDL}->{_type_ns} = ""; - $self->_wsdl_schemans and - $self->{_WSDL}->{_type_ns} .= $self->_wsdl_schemans.":|"; - - #TBD: the apache soap special case has to be handled elsewhere - $nsHash->{'http://xml.apache.org/xml-soap'} and - $self->{_WSDL}->{_type_ns} .= $nsHash->{'http://xml.apache.org/xml-soap'} . "|";; - chop $self->{_WSDL}->{_type_ns}; - + # check cache + if ( $self->{ _WSDL }->{ fileCache } ) + { + + # get xpath from cache + $xpath = $self->{ _WSDL }->{ fileCache }->get( $self->wsdl ); + + # get in-memory cache from cache + if ( $self->{ _WSDL }->{ caching } ) + { + my $cache = + $self->{ _WSDL }->{ fileCache }->get( $self->wsdl . "_cache" ); + $self->{ _WSDL }->{ cache } = $cache || {}; + } ## end if ( $self->{ _WSDL }->... + } ## end if ( $self->{ _WSDL }->... + unless ( $xpath ) + { + $xpath = + XML::XPath->new( + xml => SOAP::Schema->new( schema_url => $self->wsdl )->access ); + } ## end unless ( $xpath ) + + ( $xpath ) + || die "Error processing WSDL: Cannot create XPath object"; + + $self->_wsdl_xpath( $xpath ); + + # Get root element () and get + # default prefix (the root element's one). + my $definitions = undef; + $definitions = $xpath->find( '/*[1]' )->shift; + + my $prefix = $definitions->getPrefix; + $self->_wsdl_wsdlns( $prefix ? $prefix . ':' : '' ); + + # get the targetNamespace + my $tns = $definitions->getAttribute( 'targetNamespace' ) + || die + "Error processing WSDL: cannot get "; + + # look for schema namespace & prefix for targetNamespace + my ( $defaultNS, $schemaNS ) = ( '', '' ); + my @_ns_sub_list = (); + + my $nameSpaces = $definitions->getNamespaces + || die "Error processing WSDL: cannot get namespaces"; + my $nsHash = {}; + foreach my $ns ( @{ $nameSpaces } ) + { + $xpath->set_namespace( $ns->getPrefix, $ns->getData ); + if ( $ns->getData eq $tns ) + { + push @_ns_sub_list, $ns->getPrefix; + next; + } + + #------------------------------------------------------- + # Here we look for the default wsdl namespace which is used *only* + # when we are looking for the arrays restrictions. + # Originally the prefix was used for this, but sometimes the prefix + # can be omitted + #------------------------------------------------------- + $ns->getPrefix eq "#default" and next; + if ( $ns->getData eq "http://schemas.xmlsoap.org/wsdl/" ) + { + $self->_wsdl_wsdlExplicitNS( + $ns->getPrefix ? $ns->getPrefix . ":" : "" ); + } + + # the schema namespace is hardcoded in to the SOAP::Constants package, + # in the Lite.pm module + if ( defined $SOAP::Constants::XML_SCHEMAS{ $ns->getData } + and $SOAP::Constants::XML_SCHEMAS{ $ns->getData } =~ + /SOAP::XMLSchema\d+/ ) + { + $schemaNS = $ns->getPrefix; + } ## end if ( defined $SOAP::Constants::XML_SCHEMAS... + $nsHash->{ $ns->getData } = $ns->getPrefix . ':'; + } ## end foreach my $ns ( @{ $nameSpaces... + + $self->_wsdl_ns( $nsHash ); + $defaultNS = join( '|', @_ns_sub_list ); + + $self->_wsdl_tns( $defaultNS ); + $self->_wsdl_tns_uri( $tns ); + $self->_wsdl_schemans( $schemaNS ); + + #--- + #-- TBD: remove all the hardcoded urls + $self->_wsdl_soapns( + $self->_wsdl_ns->{ 'http://schemas.xmlsoap.org/wsdl/soap/' } ); + + #the default namespaces for types + $self->{ _WSDL }->{ _type_ns } = ""; + $self->_wsdl_schemans + and $self->{ _WSDL }->{ _type_ns } .= $self->_wsdl_schemans . ":|"; + + #TBD: the apache soap special case has to be handled elsewhere + $nsHash->{ 'http://xml.apache.org/xml-soap' } + and $self->{ _WSDL }->{ _type_ns } .= + $nsHash->{ 'http://xml.apache.org/xml-soap' } . "|"; + chop $self->{ _WSDL }->{ _type_ns }; + + # TODO make _get_first_port conditional... + $self->_get_first_port(); + + $self->servicename( $opt{ servicename } ) if ( $opt{ servicename } ); + $self->portname( $opt{ portname } ) if ( $opt{ portname } ); + + # return something useful to ease testing... + return $self; + +} ## end sub wsdlinit + +sub call +{ + my $self = shift; + my $method = shift; + my %data = @_; + + my $path; + my $location; + my $mode = 'input'; + + my $tns = $self->_wsdl_tns; + my $ns = $self->_wsdl_ns; + + my $xpath = $self->_wsdl_xpath; + + ( $xpath ) || do + { + $self->wsdlinit; + $xpath = $self->_wsdl_xpath + || die "Error processing WSDL: no wsdl object"; + }; + + my $portType = ""; + my $binding = ""; + + my $portName = ""; + + $portName = $self->portname; + $portName or die "Error processing the call: no port found"; + + #look for the binding + $path = join( + $self->_wsdl_wsdlns, + ( + "/", "definitions/", + "service[\@name='" . ( $self->servicename ) . "']/", + "port[\@name='" . $portName . "']" + ) + ); + + my $port = $xpath->find( $path )->shift + || die "Error processing WSDL file - no such port ($path)"; + + $binding = $port->findvalue( '@binding' ) + || die + "Error processing WSDL: Cannot find the binding for the service $path"; + + #look for the location + $path .= "/" . $self->_wsdl_soapns . "address"; + + my $address = $xpath->find( $path )->shift + || die "Error processing WSDL file - no such address ($path)"; + + $location = $address->findvalue( '@location' )->value + || die +"Error processing WSDL: Cannot find the port for the location in service $path"; + $self->proxy( $location ); + + # remove the default targetNamespace from messageName + $binding =~ s/^($tns)\:*//; + $binding =~ s/^($tns)\:*//; + + $path = join( + $self->_wsdl_wsdlns, + ( '/', 'definitions/', "binding[\@name='$binding']/\@type" ) + ); + + $portType = $self->_wsdl_findvalue( $path, "dieIfError" ); + $portType =~ s/^(.*?)\://; + + #Now we need to find the operation, in the binding. + #After that we can extract the SoapAction and the + #input name, if defined + + $path = join( + $self->_wsdl_wsdlns, + ( + '/', 'definitions/', "binding[\@name='$binding']/", + "operation[\@name='$method']/", $mode + ) + ); + + #overload: the user has provided a the input name for us + $data{ "wsdl_${mode}_name" } + and $path .= "[\@name='" . $data{ "wsdl_${mode}_name" } . "']"; + + #now we can get the soapaction + my $soapActionPath = + "$path/../" . $self->_wsdl_soapns . "operation/\@soapAction"; + my $soapAction = $self->_wsdl_findvalue( $soapActionPath, "" ); + $soapAction and $self->on_action( sub { sprintf "$soapAction" } ); + + #if defined, the input message name has to be the leading item + #in the SOAP call. If not defined, it has to be the operation + #name. In the case of overloaded calls, it *IS* the parameter passed + #by the calling script. So + my $inputMessageName; + if ( $data{ "wsdl_${mode}_name" } ) + { + $inputMessageName = $data{ "wsdl_${mode}_name" }; + } + else + { + $inputMessageName = $self->_wsdl_findvalue( "$path/\@name", "" ); + } + $inputMessageName or $inputMessageName = $method; + + $path = join( + $self->_wsdl_wsdlns, + ( + '/', 'definitions/', + "binding[\@name='$binding']/", "operation[\@name='$method']/", + "$mode/" + ) + ) + . $self->_wsdl_soapns . "body/"; + + #a call can have an associated, namespace + my $callNamespace = $self->_wsdl_findvalue( "$path\@namespace", "" ); + $callNamespace or $callNamespace = $self->_wsdl_tns_uri; + + #the encoding style is required when handling restricted complextypes + my $encodingStyle = ""; + $encodingStyle = $self->_wsdl_findvalue( "$path\@encodingStyle", "" ); + $encodingStyle + and $self->wsdl_encoding( $self->_wsdl_ns->{ $encodingStyle } ); + + $path = join( + $self->_wsdl_wsdlns, + ( + '/', 'definitions/', + "portType[\@name='$portType']/", + "operation[\@name='$method']/", $mode + ) + ); + + #overload: the calling script has to say wich overloading + #procedure call has to be encoded and forwarded to the server + $data{ "wsdl_${mode}_name" } + and $path .= "[\@name='" . $data{ "wsdl_${mode}_name" } . "']"; + + $path .= "/\@message"; + my $messageName = $self->_wsdl_findvalue( $path, "dieIfError" ); + + $messageName =~ s/^($tns)\:*//; + + $path = join( + $self->_wsdl_wsdlns, + ( '/', 'definitions/', "message[\@name='$messageName']/", 'part' ) + ); + +#An operation without parts is equivalent to a procedure call without parameters + my $parts = $self->_wsdl_find( $path ); + + my @param = (); + while ( my $part = $parts->shift ) + { + my @enc = $self->encode( $part, \%data ); + push @param, @enc if ( @enc ); + } + + my $methodEncoded = + SOAP::Data->name( $inputMessageName ) + ->attr( { "xmlns" => $callNamespace } ); + unless ( $self->{ _WSDL }->{ no_dispatch } ) + { + return $self->SUPER::call( + $methodEncoded => @param, + @{ $data{ soap_headers } } + ); + } ## end unless ( $self->{ _WSDL }->... + else + { + return $methodEncoded, @param; + } +} ## end sub call + +# find the first servie and port for convenience - mai go wrong, +# as it assomes the section in the +# is named equally to the section + +sub _get_first_port +{ + my $self = shift; + my $url = shift; + my $xpath = $self->_wsdl_xpath(); + + my $path = + '/definitions/service' . '/port/' . $self->_wsdl_soapns . 'address'; + + my @ports = $xpath->findnodes( $path ); + + return if ( not @ports ); + + my $address = shift @ports; + my $port = $address->getParentNode(); + my $service = $port->getParentNode(); + $self->servicename( $service->getAttribute( 'name' ) ); + $self->portname( $port->getAttribute( 'name' ) ); + + return $self; +} ## end sub _get_first_port + +sub DESTROY +{ + my $self = shift; + $self->wsdl_cache_store(); + return 1; } -sub call { - my $self=shift; - my $method=shift; - my %data=@_; - - my $path; - my $location; - my $mode='input'; - - my $tns=$self->_wsdl_tns; - my $ns=$self->_wsdl_ns; - - my $xpath=$self->_wsdl_xpath; - - ($xpath) || do { - $self->wsdlinit; - $xpath=$self->_wsdl_xpath || die "Error processing WSDL: no wsdl object"; - }; - - my $portType = ""; - my $binding = ""; - - my $portName = ""; - - $portName = $self->portname; - $portName or die "Error processing the call: no port found"; - - #look for the binding - $path = join( $self->_wsdl_wsdlns, - ("/", - "definitions/", - "service[\@name='".($self->servicename)."']/", - "port[\@name='".$portName."']")); - - my $port =$xpath->find($path)->shift - || die "Error processing WSDL file - no such port ($path)"; - - $binding = $port->findvalue('@binding') - || die "Error processing WSDL: Cannot find the binding for the service $path"; - - #look for the location - $path .= "/".$self->_wsdl_soapns. "address"; - - my $address =$xpath->find($path)->shift - || die "Error processing WSDL file - no such address ($path)"; - - $location = $address->findvalue('@location')->value - || die "Error processing WSDL: Cannot find the port for the location in service $path"; - $self->proxy($location); - - # remove the default targetNamespace from messageName - $binding=~s/^($tns)\:*//; - $binding =~ s/^($tns)\:*//; - - $path = join ($self->_wsdl_wsdlns, - ('/', - 'definitions/', - "binding[\@name='$binding']/\@type")); - - $portType = $self->_wsdl_findvalue($path,"dieIfError"); - $portType =~s/^(.*?)\://; - - #Now we need to find the operation, in the binding. - #After that we can extract the SoapAction and the - #input name, if defined - - $path= join ($self->_wsdl_wsdlns, - ('/', - 'definitions/', - "binding[\@name='$binding']/", - "operation[\@name='$method']/", - $mode)); - - #overload: the user has provided a the input name for us - $data{"wsdl_${mode}_name"} and - $path .= "[\@name='".$data{"wsdl_${mode}_name"}."']"; - - #now we can get the soapaction - my $soapActionPath = "$path/../".$self->_wsdl_soapns."operation/\@soapAction"; - my $soapAction=$self->_wsdl_findvalue($soapActionPath,""); - $soapAction and $self->on_action(sub {sprintf "$soapAction"}); - - - #if defined, the input message name has to be the leading item - #in the SOAP call. If not defined, it has to be the operation - #name. In the case of overloaded calls, it *IS* the parameter passed - #by the calling script. So - my $inputMessageName; - if($data{"wsdl_${mode}_name"}){ - $inputMessageName = $data{"wsdl_${mode}_name"}; - }else{ - $inputMessageName = $self->_wsdl_findvalue("$path/\@name",""); - } - $inputMessageName or $inputMessageName = $method; - - $path= join ($self->_wsdl_wsdlns, - ('/', - 'definitions/', - "binding[\@name='$binding']/", - "operation[\@name='$method']/", - "$mode/")). - $self->_wsdl_soapns."body/"; - - #a call can have an associated, namespace - my $callNamespace = $self->_wsdl_findvalue("$path\@namespace",""); - $callNamespace or $callNamespace = $self->_wsdl_tns_uri; - - #the encoding style is required when handling restricted complextypes - my $encodingStyle = ""; - $encodingStyle = $self->_wsdl_findvalue("$path\@encodingStyle",""); - $encodingStyle and $self->wsdl_encoding($self->_wsdl_ns->{$encodingStyle}); - - $path = join ($self->_wsdl_wsdlns, - ('/', - 'definitions/', - "portType[\@name='$portType']/", - "operation[\@name='$method']/", - $mode)); - - - #overload: the calling script has to say wich overloading - #procedure call has to be encoded and forwarded to the server - $data{"wsdl_${mode}_name"} and - $path .= "[\@name='".$data{"wsdl_${mode}_name"}."']"; - - $path .= "/\@message"; - my $messageName = $self->_wsdl_findvalue($path,"dieIfError"); - - $messageName =~ s/^($tns)\:*//; - - $path= join( $self->_wsdl_wsdlns, - ('/', - 'definitions/', - "message[\@name='$messageName']/", - 'part')); - - #An operation without parts is equivalent to a procedure call without parameters - my $parts = $self->_wsdl_find($path); - - my @param=(); - while (my $part=$parts->shift) { - my @enc=$self->encode($part, \%data); - push @param, @enc if (@enc); - } - - my $methodEncoded=SOAP::Data->name( $inputMessageName )->attr( { "xmlns" => $callNamespace } ); - unless ($self->{_WSDL}->{no_dispatch}) { - return $self->SUPER::call($methodEncoded => @param,@{$data{soap_headers}}); - } else { - return $methodEncoded, @param; - } -} - -sub DESTROY { - my $self=shift; - $self->wsdl_cache_store(); - return 1; -} - - #a sort of autoload for the store-and-return methods -sub _load_method{ - my $method = shift; - my $param = shift; - no strict "refs"; - *$method = sub{ - my $self = shift; - return (@_) ? $self->{_WSDL}->{$param} = shift : $self->{_WSDL}->{$param}?$self->{_WSDL}->{$param}:""}; -} - -&_load_method( "no_dispatch" , "no_dispatch" ); -&_load_method( "wsdl" , "wsdl" ); -&_load_method( "wsdl_checkoccurs" , "checkoccurs" ); -&_load_method( "servicename" , "servicename" ); -&_load_method( "portname" , "portname" ); -&_load_method( "wsdl_cache_directory" , "cache_directory" ); -&_load_method( "wsdl_encoding" , "wsdl_encoding" ); -&_load_method( "_wsdl_ns" , "namespaces" ); -&_load_method( "_wsdl_xpath" , "xpath" ); -&_load_method( "_wsdl_tns" , "tns" ); -&_load_method( "_wsdl_tns_uri" , "tns_uri" ); -&_load_method( "_wsdl_wsdlns" , "wsdlns" ); -&_load_method( "_wsdl_schemans" , "schemans" ); -&_load_method( "_wsdl_soapns" , "soapns" ); -&_load_method( "_wsdl_wsdlExplicitNS" , "wsdl_wsdlExplicitNS" ); +sub _load_method +{ + my $method = shift; + my $param = shift; + no strict "refs"; + *$method = sub { + my $self = shift; + return ( @_ ) ? $self->{ _WSDL }->{ $param } = shift + : $self->{ _WSDL }->{ $param } ? $self->{ _WSDL }->{ $param } + : ""; + }; +} ## end sub _load_method +&_load_method( "no_dispatch", "no_dispatch" ); +&_load_method( "wsdl", "wsdl" ); +&_load_method( "wsdl_checkoccurs", "checkoccurs" ); +&_load_method( "servicename", "servicename" ); +&_load_method( "portname", "portname" ); +&_load_method( "wsdl_cache_directory", "cache_directory" ); +&_load_method( "wsdl_encoding", "wsdl_encoding" ); +&_load_method( "_wsdl_ns", "namespaces" ); +&_load_method( "_wsdl_xpath", "xpath" ); +&_load_method( "_wsdl_tns", "tns" ); +&_load_method( "_wsdl_tns_uri", "tns_uri" ); +&_load_method( "_wsdl_wsdlns", "wsdlns" ); +&_load_method( "_wsdl_schemans", "schemans" ); +&_load_method( "_wsdl_soapns", "soapns" ); +&_load_method( "_wsdl_wsdlExplicitNS", "wsdl_wsdlExplicitNS" ); #each call to make finder returns a wrapped version of the xpath calls. #find, findvalue, findnodes and so on #the cache checking part is hidden here -sub _make_finder(){ - my ($method,$call) = @_; - no strict "refs"; - *$method = sub { - my $self = shift; - my ($path,$dieIfError) = @_; - my $data = ""; +sub _make_finder() +{ + my ( $method, $call ) = @_; + no strict "refs"; + *$method = sub { + my $self = shift; + my ( $path, $dieIfError ) = @_; + my $data = ""; - $data = $self->{_WSDL}->{ cache }->{ $path }; - unless ($data) { - $data = $self->_wsdl_xpath->$call($path); - $self->{_WSDL}->{ cache }->{ $path }= $data if ($self->{_WSDL}->{ caching }); - } - if(!$data){ - $dieIfError and print("Error processing WSDL: can't find the path '$path'\n"),exit; - } - return $data; - }; -} + $data = $self->{ _WSDL }->{ cache }->{ $path }; + unless ( $data ) + { + $data = $self->_wsdl_xpath->$call( $path ); + $self->{ _WSDL }->{ cache }->{ $path } = $data + if ( $self->{ _WSDL }->{ caching } ); + } ## end unless ( $data ) + if ( !$data ) + { + $dieIfError + and + print( "Error processing WSDL: can't find the path '$path'\n" ), + exit; + } ## end if ( !$data ) + return $data; + }; +} ## end sub _make_finder() -&_make_finder("_wsdl_find","find"); -&_make_finder("_wsdl_findvalue","findvalue"); -&_make_finder("_wsdl_findnodes","findnodes"); +&_make_finder( "_wsdl_find", "find" ); +&_make_finder( "_wsdl_findvalue", "findvalue" ); +&_make_finder( "_wsdl_findnodes", "findnodes" ); +sub wsdl_cache_store +{ + my $self = shift; + if ( ( $self->{ _WSDL }->{ cache_directory } ) + && ( $self->{ _WSDL }->{ fileCache } ) ) + { + $self->{ _WSDL }->{ fileCache } + ->set( $self->wsdl, $self->{ _WSDL }->{ xpath } ); + $self->{ _WSDL }->{ fileCache } + ->set( $self->wsdl . "_cache", $self->{ _WSDL }->{ cache } ); + } ## end if ( ( $self->{ _WSDL ... +} ## end sub wsdl_cache_store -sub wsdl_cache_store { - my $self=shift; - if ( ( $self->{ _WSDL }->{ cache_directory }) && ($self->{ _WSDL }->{ fileCache } ) ) { - $self->{ _WSDL }->{ fileCache }->set( $self->wsdl, $self->{ _WSDL }->{ xpath } ); - $self->{ _WSDL }->{ fileCache }->set( $self->wsdl."_cache", $self->{ _WSDL }->{ cache } ); - } -} - -sub wsdl_cache_init { - my $self=shift; - my $p=shift || {}; # get custom params - or none... +sub wsdl_cache_init +{ + my $self = shift; + my $p = shift || {}; # get custom params - or none... my $cache = undef; eval { require Cache::FileCache; }; - if ($@) { - # warn about missing Cache::FileCache and set cache hadnle to undef - warn "File caching is enabled, but you do not have the " - . "Cache::FileCache module. Disabling Filesystem caching." - if ($self->{_WSDL}->{ cache_directory }); - $self->{_WSDL}->{ fileCache } = undef; - } else { - # initialize cache from custom parameters if given - $p->{ cache_root } ||= $self->{_WSDL}->{ cache_directory }; - $cache=Cache::FileCache->new( $p ); - } - $self->{_WSDL}->{ fileCache } = $cache; -} + if ( $@ ) + { -sub encode { + # warn about missing Cache::FileCache and set cache hadnle to undef + warn "File caching is enabled, but you do not have the " + . "Cache::FileCache module. Disabling Filesystem caching." + if ( $self->{ _WSDL }->{ cache_directory } ); + $self->{ _WSDL }->{ fileCache } = undef; + } ## end if ( $@ ) + else + { - my $self=shift; - my $part=shift; - my $data=shift; - - my $schemaNS=$self->_wsdl_schemans ? $self->_wsdl_schemans .':' : ''; - my $defaultNS=$self->{_WSDL}->{tns}; - - my %nsHash = reverse %{ $self->_wsdl_ns }; - my %nsURIs = %{ $self->_wsdl_ns }; - - #TBD: Caching hook ? - my $p={ - name => $part->findvalue('@name')->value, - type => $part->findvalue('@type')->value, - element => $part->findvalue('@element')->value, - xmlns => $part->findvalue('@targetNamespace')->value, - nillable => $part->findvalue('@nillable')->value, - }; - - my $result = undef; - my $order = undef; - my $typeName = undef; - my $typeNS = ""; - my $type = ""; + # initialize cache from custom parameters if given + $p->{ cache_root } ||= $self->{ _WSDL }->{ cache_directory }; + $cache = Cache::FileCache->new( $p ); + } ## end else [ if ( $@ ) + $self->{ _WSDL }->{ fileCache } = $cache; +} ## end sub wsdl_cache_init +sub encode +{ - my $default = $part->findvalue('@default')->value; - if($default eq "0" or $default){ - $p->{default} = $default; - } + my $self = shift; + my $part = shift; + my $data = shift; - if ( ($p->{type}) ) { - if($p->{type}=~ m!($defaultNS):(.*)!){ - $typeName = $2; + my $schemaNS = $self->_wsdl_schemans ? $self->_wsdl_schemans . ':' : ''; + my $defaultNS = $self->{ _WSDL }->{ tns }; - #looking for type restrictions - my $path= join ($self->_wsdl_wsdlns, - '/', - 'definitions/', - "types/${schemaNS}schema/") - ."${schemaNS}simpleType[\@name='$typeName']/" - ."${schemaNS}restriction". - "|". - join ($self->_wsdl_wsdlns, - '/', - 'definitions/', - "types/${schemaNS}schema/") - ."${schemaNS}complexType[\@name='$typeName']/${schemaNS}complexContent/" - ."${schemaNS}restriction"; + my %nsHash = reverse %{ $self->_wsdl_ns }; + my %nsURIs = %{ $self->_wsdl_ns }; - #usually there is only one restriction - #my $simpleType = $self->{_WSDL}->{xpath}->find($path)->shift; - my $simpleType = $self->_wsdl_find($path,"")->shift; - $simpleType and my $baseType = $simpleType->findvalue('@base')->value; + #TBD: Caching hook ? + my $p = { + name => $part->findvalue( '@name' )->value, + type => $part->findvalue( '@type' )->value, + element => $part->findvalue( '@element' )->value, + xmlns => $part->findvalue( '@targetNamespace' )->value, + nillable => $part->findvalue( '@nillable' )->value, + }; - #TBD: verify if the data matches the restrictions - #-- - #now we have (hopely) the base type - my $wsdl_encoding = $self->wsdl_encoding(); - - if(defined ($baseType) and $baseType eq $wsdl_encoding."Array"){ - #the type is an array restricted of something - #-- - $type=$baseType; - $type=~s/^$schemaNS/xsd:/; - #-- + my $result = undef; + my $order = undef; + my $typeName = undef; + my $typeNS = ""; + my $type = ""; - #if the basetype is Array then we ask: Array of what? - #only complexTypes can be restricted to an Array - my $path = join ($self->_wsdl_wsdlns, - '/', - 'definitions/', - "types/${schemaNS}schema/") - ."${schemaNS}complexType[\@name='$typeName']/${schemaNS}complexContent/" - ."${schemaNS}restriction/". - "${schemaNS}attribute"; - - my $simpleType = $self->{_WSDL}->{xpath}->find($path)->shift; - $simpleType and $baseType = $simpleType->findvalue('@'.($self->_wsdl_wsdlExplicitNS).'arrayType')->value; - - #and now we have (eventually) the base type - $baseType =~ s/..$//; - } - $baseType and $p->{type} = $baseType; - } - - #Now, we have p, and p has a type - #and the type of p is (eventually) extracted from some restriction - - #In order to get the correct type, now we have to handle the imported - #namespaces. Some wsdl files have multiple schema declaration. - #And each declaration can have her own imported namespaces. - #Plainly: for each type check we have to check the schemas chain - #in order to get the imported namespaces for *that* schema - - #- _type_ns contains the typical default namespaces - $typeNS = $self->{_WSDL}->{_type_ns}; - - $p->{type} =~ /(.*:)(.*)/; - if($1 ne $schemaNS){ - #the type of p don't belongs to some default schema type - #first we look after the schema who owns our type - my $path= join ($self->_wsdl_wsdlns, - '/', - 'definitions/', - "types/${schemaNS}schema/"). - "*[\@name='$2']"; - - - my $schema = $self->_wsdl_find("$path/..","")->shift; - my $nodeSet = $self->_wsdl_find("$path/preceding-sibling::${schemaNS}import"); - while (my $node = $nodeSet->shift){ - no warnings; - $typeNS .= "|".$self->_wsdl_ns->{$node->getAttribute('namespace')}; - } - - #if the schema has a default nameSpace, it has to be added to the added to dhe typeNs list - - my $schemaTargetNS = $schema->findvalue('@targetNamespace'); - if( $schemaTargetNS){ - defined $self->_wsdl_ns->{$schemaTargetNS} and - $typeNS.= "|".$self->_wsdl_ns->{$schemaTargetNS}; - } - } - - if ( $p->{type}=~m/^$typeNS/ ){#it's a simple type - - #symple types can have default values - if(!exists $data->{ $p->{ name }} or !defined $data->{ $p->{ name }}){ - if(defined $p->{default}){ - $data->{ $p->{ name }} = $p->{default}; - } - } - - #-- this stuff is supposed to check the occurrences - my $count=-1; - if ($self->{_WSDL}->{checkoccurs}) { - $count= exists $data->{ $p->{ name } } ? - defined $data->{ $p->{ name } } ? - ref $data->{ $p->{ name } } eq 'ARRAY' ? - scalar @{ $data->{ $p->{ name } } } - : 1 - : 0 - : 0 ; - - $order=$part->getParentNode()->getName; - $p->{ minOccurs }=$part->findvalue('@minOccurs')->value; - if ( (! defined($p->{ minOccurs }) ) || ( $p->{ minOccurs } eq "") ) { - if ($order eq 'sequence') { - $p->{ minOccurs }=1; - }elsif ($order eq 'all'){ - $p->{ minOccurs }=0; - }else { - $p->{ minOccurs }=0; - } + my $default = $part->findvalue( '@default' )->value; + if ( $default eq "0" or $default ) + { + $p->{ default } = $default; } + if ( ( $p->{ type } ) ) + { + if ( $p->{ type } =~ m!($defaultNS):(.*)! ) + { + $typeName = $2; - $p->{ maxOccurs }=$part->findvalue('@maxOccurs')->value; - if ( (! defined($p->{ maxOccurs }) ) || ($p->{ maxOccurs } eq "") ) { - if ($order eq 'sequence') { $p->{ maxOccurs }=1 } - elsif ($order eq 'all') { $p->{ maxOccurs }=1 } - else { $p->{ maxOccurs }=undef } - } - $p->{ maxOccurs } = undef if (defined($p->{ maxOccurs }) && $p->{ maxOccurs } eq 'unbounded' ); - } - - # check for ocurrence ? - # acceptable number of value ? - if ( (! $self->{_WSDL}->{checkoccurs} ) - || ( ( $p->{ minOccurs } <=$count ) || ($p->{ nillable } eq "true") ) - && ( ( !defined($p->{ maxOccurs }) ) || ($count <= $p->{ maxOccurs }) ) ) { - # not nillable - # empty value - ( ! $p->{ nillable } ) && - ( ( ! (exists $data->{ $p->{ name } }) ) || (! (defined $data->{ $p->{ name } }) ) ) - && do { - return (); - }; - # some value - - # SOAP::Lite uses the "xsd" prefix for specifying schema NS - my $type=$p->{ type }; - $type=~s/^$schemaNS/xsd:/; - $result=SOAP::Data->new( name => $p->{ name } ); - $result->type( $type ) if ($self->autotype); - $result->attr( { xmlns => $p->{ xmlns } } ) if $p->{ xmlns }; - return ($result->value( $data->{ $p->{ name } } ) ); - } else { - no warnings; - die "illegal number of elements ($count, min: ".$p->{ minOccurs } .", max: .".$p->{ maxOccurs }.") for element '$p->{ name }' (may be sub-element) "; - } - - } else { ### must be a complex type - ### get complex type - my $type=$p->{ type }; - $type=~s/^($defaultNS)\://; # - $type=~s/^(.+?\:)?//; - my $path; - { - no warnings; - - $path='/'.$self->_wsdl_wsdlns.'definitions/' - .$self->_wsdl_wsdlns."types/${schemaNS}schema/" - ."${schemaNS}complexType[\@name='$type']" - .'|' - .'/'.$self->_wsdl_wsdlns.'definitions/' - .$self->_wsdl_wsdlns."types/schema[\@xmlns='". $nsHash{$schemaNS} ."' and \@targetNameSpace = '". $nsHash{$1}."' ]/" - ."complexType[\@name='$type']" - ; - }; - - my $complexType = $self->_wsdl_find($path,"dieIfError")->shift; + #looking for type restrictions + my $path = join( $self->_wsdl_wsdlns, + '/', 'definitions/', "types/${schemaNS}schema/" ) + . "${schemaNS}simpleType[\@name='$typeName']/" + . "${schemaNS}restriction" . "|" + . join( $self->_wsdl_wsdlns, + '/', 'definitions/', "types/${schemaNS}schema/" ) + . "${schemaNS}complexType[\@name='$typeName']/${schemaNS}complexContent/" + . "${schemaNS}restriction"; - ### handles arrays of complex types - ### TBD: check for min /max number of elements - if (ref $data->{ $p->{ name } } eq 'ARRAY') { - #$data says: look, in this position I have for you an array of stuff - my @resultArray=(); - foreach my $subdata( @{ $data->{ $p->{ name } } }) { - $result = SOAP::Data->new( name => $p->{ name } ); - $result->type( $type ) if ($self->autotype); - $result->attr( { xmlns => $p->{ xmlns } } ) if $p->{ xmlns }; - my $value = $self->_encodeComplexType($complexType, $subdata ); - push @resultArray, $result->value( $value ) if (defined($value) ); - } - return (@resultArray) ? @resultArray : (); - } else { - $result=SOAP::Data->new( name => $p->{ name } ); - #.Net compatibility $result->type( $type ) if ($self->autotype); - $result->attr( { xmlns => $p->{ xmlns } } ) if $p->{ xmlns }; - my $value; + #usually there is only one restriction + #my $simpleType = $self->{_WSDL}->{xpath}->find($path)->shift; + my $simpleType = $self->_wsdl_find( $path, "" )->shift; + $simpleType + and my $baseType = $simpleType->findvalue( '@base' )->value; - # - if($data->{ $p->{ name } }){#we have some data to encode - $value = $self->_encodeComplexType($complexType, $data->{ $p->{ name } } ) ; - }else{ - $p->{ minOccurs } = $part->findvalue('@minOccurs')->value; - if($p->{ minOccurs } ne '' and $p->{ minOccurs } > 0){ - #this element is required, but we have no data to encode - #it's an error - die "illegal number of elements (0, min: ".$p->{ minOccurs } .", for element '$p->{ name }' (may be sub-element) "; - } - } - return () unless ( defined($value) ); - return ($result->value( $value)); - } - } - } elsif ($p->{ element } ) { - - #if p has no type, then must be an an element (or an error) - #which one? - my $elementPath=$p->{element}; + #TBD: verify if the data matches the restrictions + #-- + #now we have (hopely) the base type + my $wsdl_encoding = $self->wsdl_encoding(); - $elementPath=~s/^$defaultNS\://; - # there are two ways how schema are usually defined - my $path= '/'.$self->_wsdl_wsdlns.'definitions/' - .$self->_wsdl_wsdlns.'types/' - .$schemaNS.'schema/' - .$schemaNS.'element[@name="'.$elementPath.'"]/' - .$schemaNS.'complexType/' - .'descendant::'.$schemaNS.'element'; - - my $elements = $self->_wsdl_findnodes($path,"dieIfError"); - - my @resultArray=(); - while (my $e=$elements->shift) { - my @enc; - @enc=$self->encode($e, $data); - push @resultArray, @enc if (@enc); - } - return (@resultArray) ? @resultArray : (); - } else { - #typical case when coping with .Net generated wsdl files - ($p->{name} eq "anyType") - and print "Oops, have you defined an ArrayOfAnyType without the Type? Try type=[namespace]:anyType\n"; - die "illegal part definition\n"; - } - return (); # if we got here, something went wrong... -} + if ( defined( $baseType ) + and $baseType eq $wsdl_encoding . "Array" ) + { -sub _encodeComplexType { - my $self=shift; - my $complexType=shift; - my $data = shift; - my @result = (); - my $schemaNS=$self->_wsdl_schemans ? $self->_wsdl_schemans .':' : ''; - my $defaultNS=$self->_wsdl_tns; - my %nsHash = reverse %{ $self->_wsdl_ns }; + #the type is an array restricted of something + #-- + $type = $baseType; + $type =~ s/^$schemaNS/xsd:/; - #-- first we encode the local elements .... - my $path='.//'.$schemaNS.'element'; - my $elements=$complexType->find($path); - while (my $e=$elements->shift) { - my @enc; - @enc=$self->encode($e, $data); - push @result, @enc if (@enc); - } + #-- - my $extension = undef; - ### check for extension - #%baseList avoids loops while looking at the extensions chain, just a flag holder - my %baseList = (); - #... and then we cope with the chain of extensions - while ($extension=$complexType->find('.//'.$schemaNS.'extension')->shift) { - ### pull in extension base - my $base=$extension->findvalue('@base'); - $base=~s/^$defaultNS\://; - $base=~s/^(.+?\:)//; - #- - last if ($baseList{$base}); #got a loop - $baseList{$base} = 1; - #- - my $path; - { no warnings; - # there are two ways how schema are usually defined - $path='/'.$self->_wsdl_wsdlns.'definitions/' - .$self->_wsdl_wsdlns."types/".$schemaNS."schema/" - .$schemaNS."complexType[\@name='$base']" - .'|' - .'/'.$self->_wsdl_wsdlns.'definitions/' - .$self->_wsdl_wsdlns."types/schema[\@xmlns='" - .$nsHash{$schemaNS} ."' and \@targetNameSpace = '". $nsHash{$1}."' ]/" - ."complexType[\@name='$base']"; - } - - $complexType = $self->_wsdl_find($path,"dieIfError")->shift; - #now we can find the elements - $path = ".//".$schemaNS."element|.//element"; - my $elements = $complexType->find($path) - || die "Error processing WSDL: '$path' not found"; - - while (my $e=$elements->shift) { - my @enc; - @enc=$self->encode($e, $data); - push @result, @enc if (@enc); - } - } - return (@result) ? \SOAP::Data->value(@result) : (); -} + #if the basetype is Array then we ask: Array of what? + #only complexTypes can be restricted to an Array + my $path = join( $self->_wsdl_wsdlns, + '/', 'definitions/', "types/${schemaNS}schema/" ) + . "${schemaNS}complexType[\@name='$typeName']/${schemaNS}complexContent/" + . "${schemaNS}restriction/" + . "${schemaNS}attribute"; + my $simpleType = + $self->{ _WSDL }->{ xpath }->find( $path )->shift; + $simpleType + and $baseType = + $simpleType->findvalue( + '@' . ( $self->_wsdl_wsdlExplicitNS ) . 'arrayType' ) + ->value; + #and now we have (eventually) the base type + $baseType =~ s/..$//; + } ## end if ( defined( $baseType... + $baseType and $p->{ type } = $baseType; + } ## end if ( $p->{ type } =~ m!($defaultNS):(.*)!... + + #Now, we have p, and p has a type + #and the type of p is (eventually) extracted from some restriction + + #In order to get the correct type, now we have to handle the imported + #namespaces. Some wsdl files have multiple schema declaration. + #And each declaration can have her own imported namespaces. + #Plainly: for each type check we have to check the schemas chain + #in order to get the imported namespaces for *that* schema + + #- _type_ns contains the typical default namespaces + $typeNS = $self->{ _WSDL }->{ _type_ns }; + + $p->{ type } =~ /(.*:)(.*)/; + if ( $1 ne $schemaNS ) + { + + #the type of p don't belongs to some default schema type + #first we look after the schema who owns our type + my $path = join( $self->_wsdl_wsdlns, + '/', 'definitions/', "types/${schemaNS}schema/" ) + . "*[\@name='$2']"; + + my $schema = $self->_wsdl_find( "$path/..", "" )->shift; + my $nodeSet = + $self->_wsdl_find( + "$path/preceding-sibling::${schemaNS}import" ); + while ( my $node = $nodeSet->shift ) + { + no warnings; + $typeNS .= "|" + . $self->_wsdl_ns->{ $node->getAttribute( 'namespace' ) }; + } ## end while ( my $node = $nodeSet... + +#if the schema has a default nameSpace, it has to be added to the added to dhe typeNs list + + my $schemaTargetNS = $schema->findvalue( '@targetNamespace' ); + if ( $schemaTargetNS ) + { + defined $self->_wsdl_ns->{ $schemaTargetNS } + and $typeNS .= "|" . $self->_wsdl_ns->{ $schemaTargetNS }; + } + } ## end if ( $1 ne $schemaNS ) + + if ( $p->{ type } =~ m/^$typeNS/ ) + { #it's a simple type + + #symple types can have default values + if ( !exists $data->{ $p->{ name } } + or !defined $data->{ $p->{ name } } ) + { + if ( defined $p->{ default } ) + { + $data->{ $p->{ name } } = $p->{ default }; + } + } ## end if ( !exists $data->{ ... + + #-- this stuff is supposed to check the occurrences + my $count = -1; + if ( $self->{ _WSDL }->{ checkoccurs } ) + { + $count = + exists $data->{ $p->{ name } } + ? defined $data->{ $p->{ name } } + ? ref $data->{ $p->{ name } } eq 'ARRAY' + ? scalar @{ $data->{ $p->{ name } } } + : 1 + : 0 + : 0; + + $order = $part->getParentNode()->getName; + $p->{ minOccurs } = $part->findvalue( '@minOccurs' )->value; + if ( ( !defined( $p->{ minOccurs } ) ) + || ( $p->{ minOccurs } eq "" ) ) + { + if ( $order eq 'sequence' ) + { + $p->{ minOccurs } = 1; + } + elsif ( $order eq 'all' ) + { + $p->{ minOccurs } = 0; + } + else + { + $p->{ minOccurs } = 0; + } + } ## end if ( ( !defined( $p->{... + + $p->{ maxOccurs } = $part->findvalue( '@maxOccurs' )->value; + if ( ( !defined( $p->{ maxOccurs } ) ) + || ( $p->{ maxOccurs } eq "" ) ) + { + if ( $order eq 'sequence' ) { $p->{ maxOccurs } = 1 } + elsif ( $order eq 'all' ) { $p->{ maxOccurs } = 1 } + else { $p->{ maxOccurs } = undef } + } ## end if ( ( !defined( $p->{... + $p->{ maxOccurs } = undef + if ( defined( $p->{ maxOccurs } ) + && $p->{ maxOccurs } eq 'unbounded' ); + } ## end if ( $self->{ _WSDL }->... + + # check for ocurrence ? + # acceptable number of value ? + if ( + ( !$self->{ _WSDL }->{ checkoccurs } ) + || ( ( $p->{ minOccurs } <= $count ) + || ( $p->{ nillable } eq "true" ) ) + && ( ( !defined( $p->{ maxOccurs } ) ) + || ( $count <= $p->{ maxOccurs } ) ) + ) + { + + # not nillable + # empty value + ( !$p->{ nillable } ) + && ( ( !( exists $data->{ $p->{ name } } ) ) + || ( !( defined $data->{ $p->{ name } } ) ) ) + && do + { + return (); + }; + + # some value + + # SOAP::Lite uses the "xsd" prefix for specifying schema NS + my $type = $p->{ type }; + $type =~ s/^$schemaNS/xsd:/; + $result = SOAP::Data->new( name => $p->{ name } ); + $result->type( $type ) if ( $self->autotype ); + $result->attr( { xmlns => $p->{ xmlns } } ) if $p->{ xmlns }; + return ( $result->value( $data->{ $p->{ name } } ) ); + } ## end if ( ( !$self->{ _WSDL... + else + { + no warnings; + die "illegal number of elements ($count, min: " + . $p->{ minOccurs } + . ", max: ." + . $p->{ maxOccurs } + . ") for element '$p->{ name }' (may be sub-element) "; + } ## end else [ if ( ( !$self->{ _WSDL... + + } ## end if ( $p->{ type } =~ m/^$typeNS/... + else + { ### must be a complex type + ### get complex type + my $type = $p->{ type }; + $type =~ s/^($defaultNS)\://; # + $type =~ s/^(.+?\:)?//; + my $path; + { + no warnings; + + $path = '/' + . $self->_wsdl_wsdlns + . 'definitions/' + . $self->_wsdl_wsdlns + . "types/${schemaNS}schema/" + . "${schemaNS}complexType[\@name='$type']" . '|' . '/' + . $self->_wsdl_wsdlns + . 'definitions/' + . $self->_wsdl_wsdlns + . "types/schema[\@xmlns='" + . $nsHash{ $schemaNS } + . "' and \@targetNameSpace = '" + . $nsHash{ $1 } . "' ]/" + . "complexType[\@name='$type']"; + }; + + my $complexType = $self->_wsdl_find( $path, "dieIfError" )->shift; + + ### handles arrays of complex types + ### TBD: check for min /max number of elements + if ( ref $data->{ $p->{ name } } eq 'ARRAY' ) + { + + #$data says: look, in this position I have for you an array of stuff + my @resultArray = (); + foreach my $subdata ( @{ $data->{ $p->{ name } } } ) + { + $result = SOAP::Data->new( name => $p->{ name } ); + $result->type( $type ) if ( $self->autotype ); + $result->attr( { xmlns => $p->{ xmlns } } ) + if $p->{ xmlns }; + my $value = + $self->_encodeComplexType( $complexType, $subdata ); + push @resultArray, $result->value( $value ) + if ( defined( $value ) ); + } ## end foreach my $subdata ( @{ $data... + return ( @resultArray ) ? @resultArray : (); + } ## end if ( ref $data->{ $p->... + else + { + $result = SOAP::Data->new( name => $p->{ name } ); + + #.Net compatibility $result->type( $type ) if ($self->autotype); + $result->attr( { xmlns => $p->{ xmlns } } ) if $p->{ xmlns }; + my $value; + + # + if ( $data->{ $p->{ name } } ) + { #we have some data to encode + $value = + $self->_encodeComplexType( $complexType, + $data->{ $p->{ name } } ); + } ## end if ( $data->{ $p->{ name... + else + { + $p->{ minOccurs } = + $part->findvalue( '@minOccurs' )->value; + if ( $p->{ minOccurs } ne '' and $p->{ minOccurs } > 0 ) + { + + #this element is required, but we have no data to encode + #it's an error + die "illegal number of elements (0, min: " + . $p->{ minOccurs } + . ", for element '$p->{ name }' (may be sub-element) "; + } ## end if ( $p->{ minOccurs }... + } ## end else [ if ( $data->{ $p->{ name... + return () unless ( defined( $value ) ); + return ( $result->value( $value ) ); + } ## end else [ if ( ref $data->{ $p->... + } ## end else [ if ( $p->{ type } =~ m/^$typeNS/... + } ## end if ( ( $p->{ type } ) ... + elsif ( $p->{ element } ) + { + + #if p has no type, then must be an an element (or an error) + #which one? + my $elementPath = $p->{ element }; + + $elementPath =~ s/^$defaultNS\://; + + # there are two ways how schema are usually defined + my $path = '/' + . $self->_wsdl_wsdlns + . 'definitions/' + . $self->_wsdl_wsdlns + . 'types/' + . $schemaNS + . 'schema/' + . $schemaNS + . 'element[@name="' + . $elementPath . '"]/' + . $schemaNS + . 'complexType/' + . 'descendant::' + . $schemaNS + . 'element'; + + my $elements = $self->_wsdl_findnodes( $path, "dieIfError" ); + + my @resultArray = (); + while ( my $e = $elements->shift ) + { + my @enc; + @enc = $self->encode( $e, $data ); + push @resultArray, @enc if ( @enc ); + } ## end while ( my $e = $elements... + return ( @resultArray ) ? @resultArray : (); + } ## end elsif ( $p->{ element } ) + else + { + + #typical case when coping with .Net generated wsdl files + ( $p->{ name } eq "anyType" ) + and print +"Oops, have you defined an ArrayOfAnyType without the Type? Try type=[namespace]:anyType\n"; + die "illegal part definition\n"; + } ## end else [ if ( ( $p->{ type } ) ... + return (); # if we got here, something went wrong... +} ## end sub encode + +sub _encodeComplexType +{ + my $self = shift; + my $complexType = shift; + my $data = shift; + my @result = (); + my $schemaNS = $self->_wsdl_schemans ? $self->_wsdl_schemans . ':' : ''; + my $defaultNS = $self->_wsdl_tns; + my %nsHash = reverse %{ $self->_wsdl_ns }; + + #-- first we encode the local elements .... + my $path = './/' . $schemaNS . 'element'; + my $elements = $complexType->find( $path ); + while ( my $e = $elements->shift ) + { + my @enc; + @enc = $self->encode( $e, $data ); + push @result, @enc if ( @enc ); + } ## end while ( my $e = $elements... + + my $extension = undef; + ### check for extension +#%baseList avoids loops while looking at the extensions chain, just a flag holder + my %baseList = (); + + #... and then we cope with the chain of extensions + while ( $extension = + $complexType->find( './/' . $schemaNS . 'extension' )->shift ) + { + ### pull in extension base + my $base = $extension->findvalue( '@base' ); + $base =~ s/^$defaultNS\://; + $base =~ s/^(.+?\:)//; + + #- + last if ( $baseList{ $base } ); #got a loop + $baseList{ $base } = 1; + + #- + my $path; + { + no warnings; + + # there are two ways how schema are usually defined + $path = '/' + . $self->_wsdl_wsdlns + . 'definitions/' + . $self->_wsdl_wsdlns + . "types/" + . $schemaNS + . "schema/" + . $schemaNS + . "complexType[\@name='$base']" . '|' . '/' + . $self->_wsdl_wsdlns + . 'definitions/' + . $self->_wsdl_wsdlns + . "types/schema[\@xmlns='" + . $nsHash{ $schemaNS } + . "' and \@targetNameSpace = '" + . $nsHash{ $1 } . "' ]/" + . "complexType[\@name='$base']"; + } + + $complexType = $self->_wsdl_find( $path, "dieIfError" )->shift; + + #now we can find the elements + $path = ".//" . $schemaNS . "element|.//element"; + my $elements = $complexType->find( $path ) + || die "Error processing WSDL: '$path' not found"; + + while ( my $e = $elements->shift ) + { + my @enc; + @enc = $self->encode( $e, $data ); + push @result, @enc if ( @enc ); + } ## end while ( my $e = $elements... + } ## end while ( $extension = $complexType... + return ( @result ) ? \SOAP::Data->value( @result ) : (); +} ## end sub _encodeComplexType 1; - __END__ =pod @@ -730,15 +934,13 @@ SOAP::WSDL =head1 DESCRIPTION -This is a new version of SOAP::WSDL, mainly based on the work of +This is a small update to 1.21 - autodetection of servicename and portname +are re-introduced, so users of 1.20 have no need to change their scripts. + +1.21 was a new version of SOAP::WSDL, mainly based on the work of Giovanni S Fois. -The calling interface has changed between 1.20 and 1.21, so existing code -using this module needs to be changed, too. - -See L below. - -SOAP::WSDL provides decent WSDL support for SOAP::Lite. +SOAP::WSDL provides WSDL support for SOAP::Lite. It is built as a add-on to SOAP::Lite, and will sit on top of it, forwarding all the actual request-response to SOAP::Lite - somewhat like a pre-processor. @@ -853,10 +1055,12 @@ SOAP::WSDL loads the wsdl file specified by the wsdl parameter / call using SOAP::Lite's schema method. It sets up a XPath object of that wsdl file, and subsequently queries it for namespaces, service, and port elements. -SOAP::WSDL uses the service and the port specified by the calling script. +SOAP::WSDL automatically uses the first service and port found in the WSDL. + If you want to chose a different one, you can specify the service by calling -$soap->servicename('ServiceToUse'); + $soap->servicename('ServiceToUse'); + $soap->portname('PortToUse'); =head2 call @@ -944,15 +1148,71 @@ See L and L for details. If you plan to write your own caching implementation, you should consider the following: The XPath results cache must not survive the XPath object SOAP::WSDL uses to -store the WSDL file in (this could cause memory holes - see L for details). +store the WSDL file in (this could cause memory holes - see +L for details). This never happens during normal usage - but note that you have been warned before trying to store and re-read SOAP::WSDL's internal cache. =head1 Methods +=head2 Frequently used methods + +=head3 wsdl + + $soap->wsdl('http://my.web.service.com/wsdl'); + +Use this to specify the WSDL file to use. Must be a valid (and accessible !) +url. + +You must call this before calling L. + +For time saving's sake, this should be a local file - you never know how much +time your WebService needs for delivering a wsdl file. + +=head2 wsdlinit + + $soap->wsdlinit( caching => 1, + cache_directory => '/tmp/cache' ); + +Initializes the WSDL document for usage. + +wsdlinit will die if it can't set up the WSDL file properly, so you might +want to eval{} it. + +On death, $@ will (hopefully) contain some error message like + + Error processing WSDL: no element found + +to give you a hint about what went wrong. + +wsdlinit will accept a hash of parameters with the following keys: + =over 4 -=item call +=item * caching + +enables caching if true + +=item * cache_directory + +The cache directory to use for FS caching + +=item * url + +URL to derive port and service name from. If url is given, wsdlinit will try +to find a matching service and port in the WSDL definition. + +=item * servicename + +like setting the servicename directly. See below. + +=item * portname + +like setting the servicename directly. See below. + +=back + +=head3 call $soap->call($method, %data); @@ -966,15 +1226,69 @@ On death, $@ will (hopefully) contain some error message like to give you a hint about what went wrong. -=item no_dispatch +=head2 Configuration methods + +=head3 servicename + + $soap->servicename('Service1'); + +Use this to specify a service by name. +Your wsdl contains definitions for one or more services - hou have to tell +SOAP::WSDL which one to use. + +You can call it before each method call. + +=head3 portname + + $soap->portname('Port1'); + +Your service can have one or many ports attached to it. +Each port has some operation defined in it trough a binding. +You have to tell which port of your service should be used for the +method you are calling. + +You can call it before each method call. + +=head3 wsdl_checkoccurs + +Enables/disables checks for correct number of +occurences of elements in WSDL types. The default is 1 (on). + +Turning off occurance number checking results in a sligt performance gain. + +To turn off checking for correct number of elements, call + + $soap->wsdl_checkoccurs(0); + +=head3 wsdl_encoding + +The encoding style for the SOAP call. + +=head3 cache_directory + +enables filesystem caching (in the directory specified). The directory given must be +existant, read- and writeable. + +=head3 wsdl_cache_directory + + $soap->wsdl_cache_directory( '/tmp/cache' ); + +Sets the directory used for filesystem caching and enables filesystem caching. +Passing the I parameter to wsdlinit has the same effect. + +=head2 Seldomly used methods + +The following methods are mainly used internally in SOAP::WSDL, but may +be useful for debugging and some special purposes (like forcing a cache flush +on disk or custom cache initializations). + +=head3 no_dispatch Gets/Sets the I flag. If no_dispatch is set to true value, SOAP::WSDL will not dispatch your calls to a remote server but return the SOAP::SOM object containing the call instead. -Useful for testing / debugging - -=item encode +=head3 encode # this is how call uses encode # $xpath contains a XPath object of the wsdl document @@ -996,103 +1310,18 @@ message part. You won't need to call I unless you plan to override I or want to write a new SOAP server implementation. -=item servicename - - $soap->servicename('Service1'); - -Use this to specify a service by name. -Your wsdl is the definition of a service, so you has to declare his name - -You can call it before each method call. - -=item portname - - $soap->portname('Port1'); - -Your service can have one or many ports attached to it. -Each port has some operation defined in it trough a binding. -You have to know in which porto of your service is defined the -function you are calling. - -You can call it before each method call. - -=item wsdl - - $soap->wsdl('http://my.web.service.com/wsdl'); - -Use this to specify the WSDL file to use. Must be a valid (and accessible !) url. -You must call this before calling L. - -For time saving's sake, this should be a local file - you never know how much -time your WebService needs for delivering a wsdl file. - -=item wsdlinit - - $soap->wsdlinit( caching => 1, - cache_directory => '/tmp/cache' ); - -Initializes the WSDL document for usage. - -wsdlinit will die if it can't set up the WSDL file properly, so you might want to -eval{} it. - -On death, $@ will (hopefully) contain some error message like - - Error processing WSDL: no element found - -to give you a hint about what went wrong. - -wsdlinit will accept a hash of parameters with the following keys: - -=item * wsdl_checkoccurs - -Configuration method. Enables/disables checks for correct number of -occurences of elements in WSDL types. The default is 1 (on). - -Turning off occurance number checking results in a sligt performance gain. - -To turn off checking for correct number of elements, call - - $soap->wsdl_checkoccurs(0); - -=item * wsdl_encoding - -Get/set encoding style for the SOAP call. - -=over 8 - -=item * caching - -enables caching is set to a true value - -=item * cache_directory - -enables filesystem caching (in the directory specified). The directory given must be -existant, read- and writeable. - -=back - -=item wsdl_cache_directory - - $soap->wsdl_cache_directory( '/tmp/cache' ); - -Sets the directory used for filesystem caching and enables filesystem caching. -Passing the I parameter to wsdlinit has the same effect. - -=item wsdl_cache_store - - $soap->wsdl_cache_store(); - -Stores the content of the in-memory-cache (and the XML::XPath representation of -the WSDL file) to disk. This will not have any effect if cache_directory is not set. - -=item * wsdl_cache_init +=head3 * wsdl_cache_init Initialize the WSDL file cache. Normally called from wsdlinit. For custom cache initailization, you may pass the same parameters as to Cache::FileCache->new(). -=back +=head3 wsdl_cache_store + + $soap->wsdl_cache_store(); + +Stores the content of the in-memory-cache (and the XML::XPath representation of +the WSDL file) to disk. This will not have any effect if cache_directory is not set. =head1 Notes @@ -1233,9 +1462,17 @@ See CHANGES file. This library is free software, you can distribute / modify it under the same terms as perl itself. -=head1 AUTHOR +=head1 AUTHOR> -Martin Kutter -Giovanni S. Fois giovannisfois@tiscali.it +Replace whitespace by '@' in E-Mail addresses. + + Martin Kutter + Giovanni S. Fois + +=head1 SVN information + +$LastChangedBy: kutterma $ +$HeadURL: https://svn.sourceforge.net/svnroot/soap-wsdl/SOAP-WSDL/branches/1.21/lib/SOAP/WSDL.pm $ +$Rev: 16 $ =cut diff --git a/t/98_pod_coverage.t b/t/98_pod_coverage.t index 5a0fcf6..3f5b593 100644 --- a/t/98_pod_coverage.t +++ b/t/98_pod_coverage.t @@ -1,6 +1,6 @@ use Test::More; -eval "use Test::Pod::Coverage 1.00"; -plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD" if $@; +eval { use Test::Pod::Coverage 1.08 }; +plan skip_all => "Test::Pod::Coverage 1.08 required for testing POD" if $@; BEGIN