diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..9503d03 --- /dev/null +++ b/Build.PL @@ -0,0 +1,7 @@ +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 diff --git a/CHANGES b/CHANGES index abaa2cc..3dd3c13 100644 --- a/CHANGES +++ b/CHANGES @@ -1,56 +1,146 @@ -$Log: CHANGES,v $ -Revision 1.7 2004/07/27 13:00:03 lsc -- added missing test file - -Revision 1.18 2004/07/16 07:43:05 lsc -fixed test scripts for windows - -Revision 1.17 2004/07/05 08:19:49 lsc -- added wsdl_checkoccurs - -Revision 1.16 2004/07/04 09:01:14 lsc -- change element lookup from find('/definitions') and find('wsdl:definitions') to find('/*[1]') to process arbitrary default (wsdl) namespaces correctly -- fixed test output in test 06 - -Revision 1.15 2004/07/02 12:28:31 lsc -- documentation update -- cosmetics - -Revision 1.14 2004/07/02 10:53:36 lsc -- API change: - - call now behaves (almost) like SOAP::Lite::call - - call() takes a list (hash) as second argument - - call does no longer support the "dispatch" option - - dispatching calls can be suppressed by passing - "no_dispatch => 1" to new() - - dispatching calls can be suppressed by calling - $soap->no_dispatch(1); - and re-enabled by calling - $soap->no_dispatch(0); -- Updated test skripts to reflect API change. - -Revision 1.13 2004/06/30 12:08:40 lsc -- added IServiceInstance (ecmed) to acceptance tests -- refined documentation - -Revision 1.12 2004/06/26 14:13:29 lsc -- refined file caching -- added descriptive output to test scripts - -Revision 1.11 2004/06/26 07:55:40 lsc -- fixed "freeze" caching bug -- improved test scripts to test file system caching (and show the difference) - -Revision 1.10 2004/06/26 06:30:33 lsc -- added filesystem caching using Cache::FileCache - -Revision 1.9 2004/06/24 12:27:23 lsc -Cleanup - -Revision 1.8 2004/06/11 19:49:15 lsc -- moved .t files to more self-describing names -- changed WSDL.pm to accept AXIS wsdl files -- implemented XPath query result caching on all absolute queries - -Revision 1.7 2004/06/07 13:01:16 lsc -added changelog to pod +2007/05/28 private methods made private and pod update + - added pod tests + - made encodeComplexType and method generators private + - updated pod + - fixed test scripts to work again from within/without t/ + - moved development repository to + https://svn.sourceforge.net/svnroot/soap-wsdl/ (finally !) + +2007/05/21 updated base version to customized version from Giovanni S Fois + - merged in doc changes, so that they don't refer to "customized version" + - changed build process to Build.PL + - changed repository layout to support new build process + +2006/11/06 only in the customized version + Added the support for default values in the wsdl file + +2006/11/04 only in the customized version + Changed the calling interface. Now it's driven by the by the service + and port names. + +2006/11/03 only in the customized version + Corrected the Check for the correct number of elements in complex types + + If a complex type is marked optional in a WSDL file, but sub-parts are marked as + required, SOAP::WSDL used to die if the complex type was found in the data. + Now, if a complex type has not data associated and is not strictly required, it + will not be encoded. + +A quick-and-dirty workaround is to turn off the check with + + $soap->wsdl_checkoccurs(0); + +2006/11/02 only in the customized version + - small changes for .Net compatibility when encoding complex types + - added some test cases to the test suite try perl t/3_varous.t for more details + + +2006/10/28 only in the customized version + - added a small support for the complexType restrictions of Arrays + +2006/10/02 only in the customized version + (Thanks to Dan Horne for having spotted so many bugs in a row) +- the xml prefix was used as the default wsdl namespace when looking for the complextype restrictions +- the module crashed when the operation had no part declaration +- the port name stated in the service definition was used as the portType +- the test suited failed when the module was unzipped in a t.* named directory + + + +2006/09/15 only in the customized version + (Thanks to Terje Kristensen for his support and twisted wsdl files :) +- cleaned up the code for readability +- cleaned up the code for "use warnings" and "use strict" +- added support for wsdl files with multiple schema declarations +- added supporto for some restriction on complexTypes + + + + +2006/07/10 only in the customized version + - added the support for SOAP::header calls + +2006/06/10 only in the customized version + + - removed the overload based on the type of the call's parameters + + - added an overloading support based on the unique name of the input message of the call. The input message must be provided by the calling script (sorry, the module has no mean to find this). + + - added a light support to the sympletypes. + + - added a light support to the imported namespaces in the types section. + + - added the support for the multiple SOAP bindings. The correct binding must be provided by the calling script (sorry, the module has no mean to find this). + + - the method will use his own soapAction if defined + + - the method will use his own namespace if defined + + - some other code cleaning + +2006/04/23 only in the customized version + + - corrected a bug related to the presence of the same method name on multiple + webservices + + - added an overloading support based on the type of the call's parameters + + - multiple extensions support added: a complextype can be an extension of a complextype which is an extension and so on + + + +Revision 1.7 2004/07/27 13:00:03 lsc +- added missing test file + +Revision 1.18 2004/07/16 07:43:05 lsc +fixed test scripts for windows + +Revision 1.17 2004/07/05 08:19:49 lsc +- added wsdl_checkoccurs + +Revision 1.16 2004/07/04 09:01:14 lsc +- change element lookup from find('/definitions') and find('wsdl:definitions') to find('/*[1]') to process arbitrary default (wsdl) namespaces correctly +- fixed test output in test 06 + +Revision 1.15 2004/07/02 12:28:31 lsc +- documentation update +- cosmetics + +Revision 1.14 2004/07/02 10:53:36 lsc +- API change: + - call now behaves (almost) like SOAP::Lite::call + - call() takes a list (hash) as second argument + - call does no longer support the "dispatch" option + - dispatching calls can be suppressed by passing + "no_dispatch => 1" to new() + - dispatching calls can be suppressed by calling + $soap->no_dispatch(1); + and re-enabled by calling + $soap->no_dispatch(0); +- Updated test skripts to reflect API change. + +Revision 1.13 2004/06/30 12:08:40 lsc +- added IServiceInstance (ecmed) to acceptance tests +- refined documentation + +Revision 1.12 2004/06/26 14:13:29 lsc +- refined file caching +- added descriptive output to test scripts + +Revision 1.11 2004/06/26 07:55:40 lsc +- fixed "freeze" caching bug +- improved test scripts to test file system caching (and show the difference) + +Revision 1.10 2004/06/26 06:30:33 lsc +- added filesystem caching using Cache::FileCache + +Revision 1.9 2004/06/24 12:27:23 lsc +Cleanup + +Revision 1.8 2004/06/11 19:49:15 lsc +- moved .t files to more self-describing names +- changed WSDL.pm to accept AXIS wsdl files +- implemented XPath query result caching on all absolute queries + +Revision 1.7 2004/06/07 13:01:16 lsc +added changelog to pod diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..add7d05 --- /dev/null +++ b/HACKING @@ -0,0 +1,24 @@ +Development of SOAP::WSDL takes place on sourceforge.net. + +There's a svn repository available at +https://svn.sourceforge.net/svnroot/soap-wsdl + +Engagement in the further development of this module is highly encouraged - +many people have already contributed, and many more probably will. + +I'm sometimes a bit slow in answering e-mails or merging in changes - +so if you feel your changes are urgent, please set up a sourceforge account +and ask me for commit permissions on the repository - I will happily accept +you as co-author. + +The (my) current roadmap for SOAP::WSDL is: + +1.2*: Bugfixes and support for more XSD variants + +1.3: Bindings support + +2.*: WSDL -> Perl Class factory with offline WSDL processing + +May 2007, + +Martin Kutter \ No newline at end of file diff --git a/MANIFEST b/MANIFEST index 682fd37..16b5c7b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,10 +1,15 @@ -t/1_performance.t -t/2_helloworld.NET.t -t/acceptance/helloworld.asmx.xml -t/acceptance/helloworld.xml -t/acceptance/test.wsdl.xml -WSDL.pm -MANIFEST -README -CHANGES -Makefile.PL \ No newline at end of file +Build.PL +CHANGES +HACKING +lib/SOAP/WSDL.pm +MANIFEST This list of files +META.yml +README +t/1_performance.t +t/2_helloworld.NET.t +t/3_various.t +t/97_pod.t +t/98_pod_coverage.t +t/acceptance/helloworld.asmx.xml +t/acceptance/helloworld.xml +t/acceptance/test.wsdl.xml diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..f924960 --- /dev/null +++ b/META.yml @@ -0,0 +1,22 @@ +--- +name: SOAP-WSDL +version: 1.21 +author: + - |- + Martin Kutter + Giovanni S. Fois giovannisfois@tiscali.it +abstract: WSDL support for SOAP::Lite +license: artistic +resources: + license: http://opensource.org/licenses/artistic-license.php +requires: + SOAP::Lite: 0.55 + XML::XPath: 0 +provides: + SOAP::WSDL: + file: lib/SOAP/WSDL.pm + version: 1.21 +generated_by: Module::Build version 0.28 +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.2.html + version: 1.2 diff --git a/Makefile.PL b/Makefile.PL deleted file mode 100644 index 8dd9e3e..0000000 --- a/Makefile.PL +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/perl -w -use ExtUtils::MakeMaker; -# See lib/ExtUtils/MakeMaker.pm for details of how to influence -# the contents of the Makefile that is written. -WriteMakefile( - 'NAME' => 'SOAP::WSDL', - 'VERSION_FROM' => 'WSDL.pm', # finds $VERSION - 'PREREQ_PM' => { 'XML::XPath' => 0, - 'SOAP::Lite' => 0 } -); diff --git a/README b/README index ab957f0..b77ac1c 100644 --- a/README +++ b/README @@ -1,34 +1,34 @@ -SOAP::WSDL - a WSDL-driven message preprocessor for SOAP::Lite. - -DESCRIPTION - -See "perldoc SOAP::WSDL" (or "perldoc WSDL.pm") for details. - - -PREREQUISITES - -SOAP::WSDL requires the following perl modules: - -- SOAP::Lite -- XML::XPath - - -If you want to use file system caching (improves performance), you also -need the following packages: - -- Cache::Cache - -INSTALLING - -Use the usual mantra: - -perl Makefile.PL -make -make test -make install - - -LICENSE - -This library is free software, you can distribute it under the same -terms as perl itself. +SOAP::WSDL - a WSDL-driven message preprocessor for SOAP::Lite. + +DESCRIPTION + +See "perldoc SOAP::WSDL" (or "perldoc WSDL.pm") for details. + + +PREREQUISITES + +SOAP::WSDL requires the following perl modules: + +- SOAP::Lite +- XML::XPath + + +If you want to use file system caching (improves performance), you also +need the following packages: + +- Cache::Cache + +INSTALLING + +Use the usual mantra: + +perl Makefile.PL +make +make test +make install + + +LICENSE + +This library is free software, you can distribute/modify it under the same +terms as perl itself. diff --git a/WSDL.pm b/WSDL.pm deleted file mode 100644 index d708383..0000000 --- a/WSDL.pm +++ /dev/null @@ -1,1072 +0,0 @@ -#!/usr/bin/perl -w -package SOAP::WSDL; - -use SOAP::Lite; -use vars qw($VERSION @ISA); -use XML::XPath; -# use Cache::FileCache; - -use Data::Dumper; - -# use diagnostics; - -@ISA= qw(SOAP::Lite); - -# let CVS handle this for you... -$VERSION = sprintf("%d.%02d", q$Revision: 1.20 $ =~ /(\d+)\.(\d+)/); - -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(); - }; - - my $location=$self->transport->proxy->endpoint - || die "Error processing WSDL: No port specified. ". - "You need to call port() before calling wsdlinit"; - - # 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 => $self->wsdl)->access ); - } - - ($xpath) || - die "Error processing WSDL: Cannot create XPath object"; - - # 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 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; - } - if ($ns->getData eq 'http://www.w3.org/2001/XMLSchema') { - $schemaNS=$ns->getPrefix; - } - $nsHash->{ $ns->getData } = $ns->getPrefix.':'; - } - $self->_wsdl_ns( $nsHash ); - - $defaultNS=join('|', @_ns_sub_list); - $self->_wsdl_tns($defaultNS); - $self->_wsdl_tns_uri($tns); - $self->_wsdl_schemans($schemaNS); - - - ### TBD: get portName with ONE xpath expression (yup this IS possible). - - # 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"; - - $self->_wsdl_portname($portName); - $self->_wsdl_xpath( $xpath ); -} - -sub call { - my $self=shift; - my $method=shift; - my %data=@_; - - my $path; - - my $location=$self->endpoint; - - my $mode='input'; - - my $tns=$self->_wsdl_tns; - my $ns=$self->_wsdl_ns; - ### get input|output message - - my $xpath=$self->_wsdl_xpath; - - ($xpath) || do { - $self->wsdlinit; - $xpath=$self->_wsdl_xpath || die "Error processing WSDL: no wsdl object"; - }; - ### jump to nodeset matching operation & take the first one (there is only one) - 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; - - unless ($messageName) { - $messageName=$xpath->findvalue($path) - || die "Error processing WSDL: cannot find $mode message for method '$method' ($path)"; - $self->{_WSDL}->{ cache }->{ $path }= $messageName if ($self->{_WSDL}->{ caching }); - } - - # remove default targetNamespace from messageName - $messageName=~s/^($tns)\:*//; - - $path='/' . $self->_wsdl_wsdlns . 'definitions/' - . $self->_wsdl_wsdlns . "message[\@name='$messageName']/" - . $self->_wsdl_wsdlns . 'part'; - - # try cache first, if caching is enabled - my $parts=$self->{_WSDL}->{ caching } ? $self->{_WSDL}->{ cache }->{ $path } : undef; - unless ($parts) { - $parts= $xpath->find($path); - $self->{_WSDL}->{ cache }->{ $path }= $parts if ($self->{_WSDL}->{ caching }); - } - - ($parts) || die "Error processing WSDL: No parts found for message $messageName with path '$path'"; - - my @param=(); - - while (my $part=$parts->shift) { - my @enc=$self->encode($part, \%data); - push @param, @enc if (@enc); - } - - ### TBD: encode method (fully qualified) - my $methodEncoded=SOAP::Data->name( $method )->attr( { xmlns => $self->_wsdl_tns_uri } ); - - unless ($self->{_WSDL}->{no_dispatch}) { - return $self->SUPER::call($methodEncoded => @param); - } else { - return $methodEncoded, @param; - } -} - -sub DESTROY { - my $self=shift; - $self->wsdl_cache_store(); - return 1; -} - -sub no_dispatch { - my $self = shift; - return (@_) ? $self->{_WSDL}->{no_dispatch}=shift : $self->{_WSDL}->{no_dispatch}; -} - -sub wsdl { - my $self = shift; - return (@_) ? $self->{_WSDL}->{wsdl}=shift : $self->{_WSDL}->{wsdl}; -} - -sub wsdl_checkoccurs { - my $self = shift; - return (@_) ? $self->{_WSDL}->{ checkoccurs }=shift : $self->{_WSDL}->{ checkoccurs }; -} - - -sub servicename { - my $self = shift; - return (@_) ? $self->{_WSDL}->{servicename}=shift : $self->{_WSDL}->{servicename}; -} - -sub wsdl_cache_directory { - my $self = shift; - return (@_) ? $self->{_WSDL}->{ cache_directory }=shift : $self->{_WSDL}->{ cache_directory }; -} - -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 || undef; - my $cache = undef; - eval { require Cache::FileCache; }; - if ($@) { - warn "File caching is enabled, but you do not have the Cache::FileCache module. Disabling Filesystem caching." - if ($self->{_WSDL}->{ cache_directory }); - } else { - if ( defined( $p ) ) { - $p->{ cache_root } = $self->{_WSDL}->{ cache_directory } unless ($p->{ cache_root }); - $cache=Cache::FileCache->new( $p ) if ($p->{ cache_root }); - } else { - if ($self->{_WSDL}->{ cache_directory }) { - $cache=Cache::FileCache->new( { cache_root => $self->{_WSDL}->{ cache_directory } } ); - } - } - } - $self->{_WSDL}->{ fileCache } = $cache; -} - -sub _wsdl_ns { - my $self = shift; - return (@_) ? $self->{_WSDL}->{namespaces}=shift : $self->{_WSDL}->{namespaces}; -} - -sub _wsdl_xpath { - my $self = shift; - return (@_) ? $self->{_WSDL}->{xpath}=shift : $self->{_WSDL}->{xpath}; -} - -sub _wsdl_tns{ - my $self = shift; - return (@_) ? $self->{_WSDL}->{tns}=shift : $self->{_WSDL}->{tns}; -} - -sub _wsdl_tns_uri{ - my $self = shift; - return (@_) ? $self->{_WSDL}->{tns}->{uri}=shift : $self->{_WSDL}->{tns}->{uri}; -} - -sub _wsdl_wsdlns{ - my $self = shift; - return (@_) ? $self->{_WSDL}->{wsdlns}=shift : $self->{_WSDL}->{wsdlns}; -} - -sub _wsdl_schemans{ - my $self = shift; - return (@_) ? $self->{_WSDL}->{schemans}=shift : $self->{_WSDL}->{schemans}; -} - -sub _wsdl_portname{ - my $self = shift; - return (@_) ? $self->{_WSDL}->{portname}=shift : $self->{_WSDL}->{portname}; -} - -sub encode { - my $self=shift; - my $part=shift; - my $data=shift; - - my $schemaNS=$self->{_WSDL}->{schemans}; - my $defaultNS=$self->{_WSDL}->{tns}; - - my %nsHash = reverse %{ $self->_wsdl_ns }; - my %nsURIs = %{ $self->_wsdl_ns }; - -# print Dumper %nsHash; - - # 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; - - if ( ($p->{type}) ) { - # - # it's either a XML schema type or an apache-soap type - my $match; - { - no warnings; - $match="$nsURIs{'http://www.w3.org/2001/XMLSchema'}|$nsURIs{'http://xml.apache.org/xml-soap'}"; - $match=~s/\|$//; - }; - - if ( $p->{type}=~m/^$match/ ) - { ### simple type - 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 } - } - $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 { ### complex type - ### get complex type - my $name=$p->{ type }; - $name=~s/^$defaultNS\://; - - $name=~s/^(.+?\:)?//; - my $path; - { - no warnings; - - $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']" - ; - }; - # got an absolute path - try cache first - my $complexType=$self->{_WSDL}->{ caching } ? $self->{_WSDL}->{ cache }->{ $path } : undef; - unless ($complexType) { - $complexType=$self->{_WSDL}->{xpath}->find($path)->shift - || die "Error processing WSDL: $path not found"; - $self->{_WSDL}->{ cache }->{ $path } = $complexType if ($self->{_WSDL}->{ caching }); - } - - # handle arrays of complex types - ### TBD: check for min /max number of elements - if (ref $data->{ $p->{ name } } eq 'ARRAY') { - 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 } ); - $result->type( $type ) if ($self->autotype); - $result->attr( { xmlns => $p->{ xmlns } } ) if $p->{ xmlns }; - - my $value; - $value = $self->encodeComplexType($complexType, $data->{ $p->{ name } } ) ; -# || die "error encoding ". $p->{ name } .": \n\t $@"; - return () unless ( defined($value) ); - return ($result->value( $value)); - } - } - } elsif ($p->{ element } ) { - ### get element - my $elementPath=$p->{element}; - $elementPath=~s/^$defaultNS\://; - - my $path= '/'.$self->_wsdl_wsdlns.'definitions/' - .$self->_wsdl_wsdlns.'types/' - .$schemaNS.':schema/' - .$schemaNS.':element[@name="'.$elementPath.'"]/' - .$schemaNS.':complexType/' - .'descendant::'.$schemaNS.':element' - ; - - # got an absolute path - try cache first - my $elements = $self->{_WSDL}->{caching } ? $self->{_WSDL}->{cache}->{$path} : undef; - unless ($elements) { - $elements=$self->{_WSDL}->{xpath}->findnodes($path) - || die "Error processing WSDL: '$path' not found"; - $self->{_WSDL}->{cache}->{$path}= $elements if ($self->{_WSDL}->{caching}); - } - - my @resultArray=(); - while (my $e=$elements->shift) { - my @enc; - @enc=$self->encode($e, $data); - # die "error encoding ". $p->{ name } .": \n\t$@"; - push @resultArray, @enc if (@enc); - } - return (@resultArray) ? @resultArray : (); - } else { - die "illegal part definition\n"; - } - return (); # if we got here, something went wrong... -} - -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 }; - ####################################################################### - # TBD: handle restriction here - ####################################################################### - ### check for extension - my $extension=$complexType->find('.//'.$schemaNS.'extension')->shift; - if ($extension) { - ### pull in extension base - my $base=$extension->findvalue('@base'); - $base=~s/^$defaultNS\://; - $base=~s/^(.+?\:)//; - 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']/descendant::".$schemaNS."element" - .'|' - .'/'.$self->_wsdl_wsdlns.'definitions/' - .$self->_wsdl_wsdlns."types/schema[\@xmlns='". $nsHash{$schemaNS} ."' and \@targetNameSpace = '". $nsHash{$1}."' ]/" - ."complexType[\@name='$base']/descendant::element"; - }; - - # got an absolute path - try cache first - my $elements=$self->{_WSDL}->{ caching } ? $self->{_WSDL}->{ cache }->{ $path } : undef; - - unless ($elements) { - $elements=$self->{_WSDL}->{xpath}->find($path) - || die "Error processing WSDL: '$path' not found"; - # set cache - $self->{_WSDL}->{ cache }->{ $path } = $elements if ($self->{_WSDL}->{ caching }); - }; - - while (my $e=$elements->shift) { - my @enc; - @enc=$self->encode($e, $data); - push @result, @enc if (@enc); - } - } - my $path='.//'.$schemaNS.'element'; - my $elements=$complexType->find($path) - || die "Error processing WSDL: './/".$schemaNS."element' not found"; - - while (my $e=$elements->shift) { - my @enc; - @enc=$self->encode($e, $data); # || die "$@"; - push @result, @enc if (@enc); - } - return (@result) ? \SOAP::Data->value(@result) : (); -} - -1; - - -__END__ - -=pod - -=head1 NAME - -SOAP::WSDL - -=head1 SYNOPSIS - - use SOAP::WSDL; - - my $soap=SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl' ) - ->proxy( 'http://myurl.com'); - - $soap->wsdlinit; - - my $som=$soap->call( 'method' => [ - { name => value }, - { name => 'value' } ]); - - -=head1 DESCRIPTION - -SOAP::WSDL provides decent 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. - -WSDL support means that you don't have to deal with those bitchy namespaces -some web services set on each and every method call parameter. - -It also means an end to that nasty - - SOAP::Data->name( 'Name' )->value( - SOAP::Data->name( 'Sub-Name')->value( 'Subvalue' ) - ); - -encoding of complex data. (Another solution for this problem is just iterating -recursively over your data. But that doesn't work if you need more information -[e.g. namespaces etc] than just your data to encode your parameters). - -And it means that you can use ordinary hashes for your parameters - the encording -order will be derived from the WSDL and not from your (unordered) data, thus the -problem of unordered perl-hashes and WSDL EsequenceE definitions is -solved, too. (Another solution for the ordering problem is tying your hash to a -class that provides ordered hashes - Tie::IxHash is one of them). - -=head2 Why should I use this ? - -SOAP::WSDL eases life for webservice developers who have to communicate with -lots of different web services using a reasonable big number of method calls. - -If you just want to call a hand full of methods of one web service, take -SOAP::Lite's stubmaker and modify the stuff by hand if it doesn't work right -from the start. The overhead SOAP::WSDL imposes on your calls is not worth -the time saving. - -If you need to access many web services offering zillions of methods to you, -this module should be your choice. It automatically encodes your perl data -structures correctly, based on the service's WSDL description, handling -even those complex types SOAP::Lite can't cope with. - -SOAP::WSDL also eliminates most perl E-E .NET interoperability -problems by qualifying method and parameters as they are specified in the -WSDL definition. - -=head1 USAGE - - my $soap=SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl' ); - - # or - my $soap=SOAP::WSDL->new() - $soap->wsdl('http://server.com/ws.wsdl'); - - # or - # without dispatching calls to the WebService - # - # useful for testing - my $soap=SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl', - no_dispatch => 1 ); - - # you must call proxy before you call wsdlinit - $soap->proxy( 'http://myurl.com') - - # optional (only necessary if the service you use is not the first one - # in document order in the WSDL file). - # If used, it must be called before wsdlinit. - $soap->servicename('Service1'); - - # optional, set to a false value if you don't want your - # soap message elements to be typed - $soap->autotype(0); - - # never forget to call this ! - $soap->wsdlinit; - - # with caching enabled: - $soap->wsdlinit( caching => 1); - - my $som=$soap->call( 'method' , - name => 'value' , - name => 'value' ); - - -=head1 How it works - -SOAP::WSDL takes the wsdl file specified and looks up the port for the service. -On calling a SOAP method, it looks up the message encoding and wraps all the -stuff around your data accordingly. - -Most pre-processing is done in I, the rest is done in I, which -overrides the same method from SOAP::Lite. - -=head2 wsdlinit - -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. - -The port you are using is deduced from the URL you're going to -connect to. SOAP::WSDL uses the first service (in document order) specified -in the wsdl file. If you want to chose a different one, you can specify -the service by calling - - $soap->servicename('ServiceToUse'); - -If you want to specify a service name, do it before calling I - it -has no effect afterwards. - -=head2 call - -The call method examines the wsdl file to find out how to encode the SOAP -message for your method. Lookups are done in real-time using XPath, so this -incorporates a small delay to your calls (see L -below. - -The SOAP message will include the types for each element, unless you have -set autotype to a false value by calling - - $soap->autotype(0); - -After wrapping your call into what is appropriate, SOAP::WSDL uses the I -method from SOAP::Lite to dispatch your call. - -call takes the method name as first argument, and the parameters passed to that -method as following arguments. - -B - - $som=$soap->call( "SomeMethod" => "test" => "testvalue" ); - - $som=$soap->call( "SomeMethod" => %args ); - -=head1 Caching - -SOAP::WSDL uses a two-stage caching mechanism to achieve best performance. - -First, there's a pretty simple caching mechanisms for storing XPath query results. -They are just stored in a hash with the XPath path as key (until recently, only -results of "find" or "findnodes" are cached). I did not use the obvious -L or L module here, because these use L to -store complex objects and thus incorporate a performance loss heavier than using -no cache at all. -Second, the XPath object and the XPath results cache are be stored on disk using -the L implementation. - -A filesystem cache is only used if you - - 1) enable caching - 2) set wsdl_cache_directory - -The cache directory must be, of course, read- and writeable. - -XPath result caching doubles performance, but increases memory consumption - if you lack of -memory, you should not enable caching (disabled by default). - -Filesystem caching triples performance for wsdlinit and doubles performance for the first -method call. - -The file system cache is written to disk when the SOAP::WSDL object is destroyed. -It may be written to disk any time by calling the L method - -Using both filesystem and in-memory caching is recommended for best performance and -smallest startup costs. - -=head2 Sharing cache between applications - -Sharing a file system cache among applications accessing the same web service is -generally possible, but may under some circumstances reduce performance, and under -some special circumstances even lead to errors. -This is due to the cache key algorithm used. - -SOAP::WSDL uses the SOAP endpoint URL to store the XML::XPath object of the wsdl file. -In the rare case of a web service listening on one particular endpoint (URL) but using -more than one WSDL definition, this may lead to errors when applications using -SOAP::WSDL share a file system cache. - -SOAP::WSDL stores the XPath results in-memory-cache in the filesystem cache, using the -key of the wsdl file with C<_cache> appended. Two applications sharing the file system -cache and accessing different methods of one web service could overwrite each others -in-memory-caches when dumping the XPath results to disk, resulting in a slight performance -drawback (even though this only happens in the rare case of one app being started before -the other one has had a chance to write its cache to disk). - -=head2 Controlling the file system cache - -If you want full controll over the file system cache, you can use wsdl_init_cash to -initialize it. wsdl_init_cash will take the same parameters as Cache::FileCache->new(). -See L and L for details. - -=head2 Notes - -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). -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 - -=over 4 - -=item call - - $soap->call($method, %data); - -See above. - -call will die if it can't find required elements in the WSDL file or if your data -doesn't meet the WSDL definition's requirements, 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. - -=item 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 - - # this is how call uses encode - # $xpath contains a XPath object of the wsdl document - - my $def=$xpath->find("/definitions")->shift; - my $parts=$def->find("//message[\@name='$messageName']/part"); - - my @param=(); - - while (my $part=$parts->shift) { - my $enc=$self->encode($part, \%data); - push @param, $enc if defined $enc; - } - -Does the actual encoding. Expects a XPath::NodeSet as first, a hashref containing -your data as second parameter. The XPath nodeset must be a node specifying a WSDL -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 (if none specified, the first one in document -order from the WSDL file is used). You must call this before calling L. - -=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 and looks up the web service and port to use -(the port is derived by the URL specified via SOAP::Lite's I method). - -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 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. - -=back - -=head1 Notes - -=head2 Why another SOAP module ? - -SOAP::Lite provides only some rudimentary WSDL support. This lack is not just -something unimplemented, but an offspring of the SOAP::Schema -class design. SOAP::Schema uses some complicated format to store XML Schema information -(mostly a big hashref, containing arrays of SOAP::Data and a SOAP::Parser-derived -object). This data structure makes it pretty hard to improve SOAP::Lite's -WSDL support. - -SOAP::WSDL uses XPath for processing WSDL. XPath is a query language standard for -XML, and usually a good choice for XML transformations or XML template processing -(and what else is WSDL-based en-/decoding ?). Besides, there's an excellent XPath -module (L) available from CPAN, and as SOAP::Lite uses XPath to -access elements in SOAP::SOM objects, this seems like a natural choice. - -Fiddling the kind of WSDL support implemented here into SOAP::Lite would mean -a larger set of changes, so I decided to build something to use as add-on. - -=head2 Memory consumption and performance - -SOAP::WSDL uses around twice the memory (or even more) SOAP::Lite uses for the -same task (but remember: SOAP::WSDL does things for you SOAP::Lite can't). -It imposes a slight delay for initialization, and for every SOAP method call, too. - -On my 1.4 GHz Pentium mobile notebook, the init delay with a simple -WSDL file (containing just one operation and some complex types and elements) -was around 50 ms, the delay for the first call around 25 ms and for subsequent -calls to the same method around 7 ms without and around 6 ms with XPath result caching -(on caching, see above). XML::XPath must do some caching, too - don't know where -else the speedup should come from. - -Calling a method of a more complex WSDL file (defining around 10 methods and -numerous complex types on around 500 lines of XML), the delay for the first -call was around 100 ms for the first and 70 ms for subsequent method calls. -wsdlinit took around 150 ms to process the stuff. With XPath result caching enabled, -all but the first call take around 35 ms. - -Using SOAP::WSDL on an idiotically complex WSDL file with just one method, but around -100 parameters for that method, mostly made up by extensions of complex types -(the heaviest XPath operation) takes around 1.2 s for the first call (0.9 with caching) -and around 830 ms for subsequent calls (arount 570 ms with caching). - -The actual performance loss compared to SOAP::Lite should be around 10 % less -than the values above - SOAP::Lite encodes the data for you, too (or you do -it yourself) - and encoding in SOAP::WSDL is already covered by the pre-call -delay time mentioned above. - -If you have lots of WebService methods and call each of them from time to time, -this delay should not affect your perfomance too much. If you have just one method -and keep calling it ever & ever again, you should cosider hardcoding your data -encoding (maybe even with hardcoded XML templates - yes, this may be a BIG speedup). - -=head1 LIMITATIONS - -=over - -=item * ErestrictionE - -SOAP::WSDL doesn't handle C WSDL elements yet. - -=item * bindings - -SOAP::WSDL does not care about port bindings yet. - -=item * overloading - -WSDL overloading is not supported yet. - -=back - -=head1 CAVEATS - -=head2 API change between 1.13 and 1.14 - -The SOAP::WSDL API changed significantly between versions 1.13 and 1.14. -From 1.14 on, B expects the following arguments: method name as scalar first, -method parameters as hash following. - -The B no longer recognizes the I option - to get the same behaviour, -pass C 1> to I or call - - $soap->no_dispatch(1); - -=head2 Unstable interface - -This is alpha software - everything may (and most things will) change. -But you don't have to be afraid too much - at least the I synopsis should -be stable from 1.14 on, and that is the part you'll use most frequently. - -=head1 BUGS - -=over - -=item * Check for the correct number of elements confused with complex types - -If a complex type is marked optional in a WSDL file, but sub-parts are marked as -required, SOAP::WSDL will die if the complex type is not found in the data -(because it checks only for the occurence of simple type elements). - -A quick-and-dirty workaround is to turn off the check with - - $soap->wsdl_checkoccurs(0); - -=item * Arrays of complex types are not checked for the correct number of elements - -Arrays of complex types are just encoded and not checked for correctness etc. -I don't know if I do this right yet, but output looks good. However, they are not -checked for the correct number of element (does the SOAP spec say how to -specify this ?). - -=item * +trace (and other SOAP::Lite flags) don't work - -This may be an issue with older versions of the base module (before 2.?), or with -activestate's activeperl, which do -not call the base modules I method with the flags supplied to the parent. - -There's a simple workaround: - - use SOAP::WSDL; - import SOAP::Lite +trace; - -=item * nothing else known - -But I'm sure there are some serious bugs lurking around somewhere. - -=back - -=head1 TODO - -=over - -=item Implement bindings support - -WSDL bindings are required for SOAP authentication. This is not too -hard to implement - just look up bindings and decide for each -(top level) element whether to put it into header or body. - -=item Allow use of alternative XPath implementations - -XML::XPath is a great module, but it's not a race-winning one. -XML::LibXML offers a promising-looking XPath interface. SOAP::WSDL should -support both, defaulting to the faster one, and leaving the final choice -to the user. - -=back - -=head1 CHANGES - -$Log: WSDL.pm,v $ -Revision 1.20 2004/07/29 06:56:45 lsc -removed "use" dependency on Cache::FileCache. require'ing it instead. - -Revision 1.19 2004/07/27 13:00:03 lsc -- added missing test file - -Revision 1.18 2004/07/16 07:43:05 lsc -fixed test scripts for windows - -Revision 1.17 2004/07/05 08:19:49 lsc -- added wsdl_checkoccurs - -Revision 1.16 2004/07/04 09:01:14 lsc -- change element lookup from find('/definitions') and find('wsdl:definitions') to find('/*[1]') to process arbitrary default (wsdl) namespaces correctly -- fixed test output in test 06 - -Revision 1.15 2004/07/02 12:28:31 lsc -- documentation update -- cosmetics - -Revision 1.14 2004/07/02 10:53:36 lsc -- API change: - - call now behaves (almost) like SOAP::Lite::call - - call() takes a list (hash) as second argument - - call does no longer support the "dispatch" option - - dispatching calls can be suppressed by passing - "no_dispatch => 1" to new() - - dispatching calls can be suppressed by calling - $soap->no_dispatch(1); - and re-enabled by calling - $soap->no_dispatch(0); -- Updated test skripts to reflect API change. - -Revision 1.13 2004/06/30 12:08:40 lsc -- added IServiceInstance (ecmed) to acceptance tests -- refined documentation - -Revision 1.12 2004/06/26 14:13:29 lsc -- refined file caching -- added descriptive output to test scripts - -Revision 1.11 2004/06/26 07:55:40 lsc -- fixed "freeze" caching bug -- improved test scripts to test file system caching (and show the difference) - -Revision 1.10 2004/06/26 06:30:33 lsc -- added filesystem caching using Cache::FileCache - -Revision 1.9 2004/06/24 12:27:23 lsc -Cleanup - -Revision 1.8 2004/06/11 19:49:15 lsc -- moved .t files to more self-describing names -- changed WSDL.pm to accept AXIS wsdl files -- implemented XPath query result caching on all absolute queries - -Revision 1.7 2004/06/07 13:01:16 lsc -added changelog to pod - - -=head1 COPYRIGHT - -(c) 2004 Martin Kutter - -This library is free software, you can use it under the same -terms as perl itself. - -=head1 AUTHOR - -Martin Kutter - -=cut diff --git a/lib/SOAP/WSDL.pm b/lib/SOAP/WSDL.pm new file mode 100644 index 0000000..d02c598 --- /dev/null +++ b/lib/SOAP/WSDL.pm @@ -0,0 +1,1241 @@ +#!/usr/bin/perl -w +package SOAP::WSDL; + +use SOAP::Lite; +use vars qw($VERSION @ISA); +use XML::XPath; + +use strict; +use warnings; + +use Data::Dumper; + +@ISA= qw(SOAP::Lite); + +$VERSION = "1.21"; + +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"; + + $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; + } + $nsHash->{ $ns->getData } = $ns->getPrefix.':'; + } + + $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}; + +} + +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" ); + + +#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 = ""; + + $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; + }; +} + +&_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 } ); + } +} + +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; +} + +sub encode { + + 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 = ""; + + + 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; + + #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"; + + #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: 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:/; + #-- + + #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; + } + } + + + $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; + + ### 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; + + # + 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}; + + $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... +} + +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); + } + + 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) : (); +} + + + +1; + + +__END__ + +=pod + +=head1 NAME + +SOAP::WSDL + +=head1 SYNOPSIS + + use SOAP::WSDL; + + my $soap = SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl' ); + $soap->wsdlinit(); + $soap->servicename( 'myservice' ); + $soap->portname( 'myport' ); + + my $som = $soap->call( 'method' => ( + name => 'value' , + name => 'value' ) ); + + +=head1 DESCRIPTION + +This is 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. +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. + +WSDL support means that you don't have to deal with those bitchy namespaces +some web services set on each and every method call parameter. + +It also means an end to that nasty + + SOAP::Data->name( 'Name' )->value( + SOAP::Data->name( 'Sub-Name')->value( 'Subvalue' ) + ); + +encoding of complex data. (Another solution for this problem is just iterating +recursively over your data. But that doesn't work if you need more information +[e.g. namespaces etc] than just your data to encode your parameters). + +And it means that you can use ordinary hashes for your parameters - the +encording order will be derived from the WSDL and not from your (unordered) +data, thus the problem of unordered perl-hashes and WSDL EsequenceE +definitions is solved, too. (Another solution for the ordering problem is +tying your hash to a class that provides ordered hashes - Tie::IxHash is +one of them). + +=head2 Why should I use this ? + +SOAP::WSDL eases life for webservice developers who have to communicate with +lots of different web services using a reasonable big number of method calls. + +If you just want to call a hand full of methods of one web service, take +SOAP::Lite's stubmaker and modify the stuff by hand if it doesn't work right +from the start. The overhead SOAP::WSDL imposes on your calls is not worth +the time saving. + +If you need to access many web services offering zillions of methods to you, +this module should be your choice. It automatically encodes your perl data +structures correctly, based on the service's WSDL description, handling +even those complex types SOAP::Lite can't cope with. + +SOAP::WSDL also eliminates most perl E-E .NET interoperability +problems by qualifying method and parameters as they are specified in the +WSDL definition. + +=head1 USAGE + + my $soap=SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl' ); + + # or + my $soap=SOAP::WSDL->new() + $soap->wsdl('http://server.com/ws.wsdl'); + + # or + # without dispatching calls to the WebService + # + # useful for testing + my $soap=SOAP::WSDL->new( wsdl => 'http://server.com/ws.wsdl', + no_dispatch => 1 ); + + # never forget to call this !in order to start the parsing procedure + $soap->wsdlinit(); + + # with caching enabled:don't forget the cache directory + $soap->wsdlinit( caching => 1, cache_directory =>"/tmp/cachedir"); + + # optional, set to a false value if you don't want your + # soap message elements to be typed + $soap->autotype(0); + + + # before calling you *must* specify which service use and which port call + # you must call it after wsdlinit + # you can call it multiple times, one for each call + $soap->servicename('myservice'); + $soap->portname('myport'); + + my $som=$soap->call( 'method' , + name => 'value' , + name => 'value' ); + + + # with the method overloaded (got it from the standard) + my $som=$soap->call( 'method' , + wsdl_input_name => unique_input_message_name + name => 'value' , + name => 'value' ); + + # with headers (see the SOAP documentation) + + #first define your headers + @header = (SOAP::Header->name("FirstHeader")->value("FirstValue"), + SOAP::Header->name("SecontHeader")->value("SecondValue")); + + #and then do the call. please note the backslash + my $som=$soap->call( 'method' , + name => 'value' , + name => 'value' , + "soap_headers",\@header); + + +=head1 How it works + +SOAP::WSDL takes the wsdl file specified and looks up the service and the specified port. +On calling a SOAP method, it looks up the message encoding and wraps all the +stuff around your data accordingly. + +Most pre-processing is done in I, the rest is done in I, which +overrides the same method from SOAP::Lite. + +=head2 wsdlinit + +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. +If you want to chose a different one, you can specify the service by calling + +$soap->servicename('ServiceToUse'); + +=head2 call + +The call method examines the wsdl file to find out how to encode the SOAP +message for your method. Lookups are done in real-time using XPath, so this +incorporates a small delay to your calls (see L +below. + +The SOAP message will include the types for each element, unless you have +set autotype to a false value by calling + + $soap->autotype(0); + +After wrapping your call into what is appropriate, SOAP::WSDL uses the I +method from SOAP::Lite to dispatch your call. + +call takes the method name as first argument, and the parameters passed to that +method as following arguments. + +B + + $som=$soap->call( "SomeMethod" => "test" => "testvalue" ); + + $som=$soap->call( "SomeMethod" => %args ); + +=head1 Caching + +SOAP::WSDL uses a two-stage caching mechanism to achieve best performance. + +First, there's a pretty simple caching mechanisms for storing XPath query results. +They are just stored in a hash with the XPath path as key (until recently, only +results of "find" or "findnodes" are cached). I did not use the obvious +L or L module here, because these +use L to store complex objects and thus incorporate a performance +loss heavier than using no cache at all. +Second, the XPath object and the XPath results cache are be stored on disk using +the L implementation. + +A filesystem cache is only used if you + + 1) enable caching + 2) set wsdl_cache_directory + +The cache directory must be, of course, read- and writeable. + +XPath result caching doubles performance, but increases memory consumption - if you lack of +memory, you should not enable caching (disabled by default). + +Filesystem caching triples performance for wsdlinit and doubles performance for the first +method call. + +The file system cache is written to disk when the SOAP::WSDL object is destroyed. +It may be written to disk any time by calling the L method + +Using both filesystem and in-memory caching is recommended for best performance and +smallest startup costs. + +=head2 Sharing cache between applications + +Sharing a file system cache among applications accessing the same web service is +generally possible, but may under some circumstances reduce performance, and under +some special circumstances even lead to errors. +This is due to the cache key algorithm used. + +SOAP::WSDL uses the SOAP endpoint URL to store the XML::XPath object of the wsdl file. +In the rare case of a web service listening on one particular endpoint (URL) but using +more than one WSDL definition, this may lead to errors when applications using +SOAP::WSDL share a file system cache. + +SOAP::WSDL stores the XPath results in-memory-cache in the filesystem cache, using the +key of the wsdl file with C<_cache> appended. Two applications sharing the file system +cache and accessing different methods of one web service could overwrite each others +in-memory-caches when dumping the XPath results to disk, resulting in a slight performance +drawback (even though this only happens in the rare case of one app being started before +the other one has had a chance to write its cache to disk). + +=head2 Controlling the file system cache + +If you want full controll over the file system cache, you can use wsdl_init_cash to +initialize it. wsdl_init_cash will take the same parameters as Cache::FileCache->new(). +See L and L for details. + +=head2 Notes + +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). +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 + +=over 4 + +=item call + + $soap->call($method, %data); + +See above. + +call will die if it can't find required elements in the WSDL file or if your data +doesn't meet the WSDL definition's requirements, 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. + +=item 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 + + # this is how call uses encode + # $xpath contains a XPath object of the wsdl document + + my $def=$xpath->find("/definitions")->shift; + my $parts=$def->find("//message[\@name='$messageName']/part"); + + my @param=(); + + while (my $part=$parts->shift) { + my $enc=$self->encode($part, \%data); + push @param, $enc if defined $enc; + } + +Does the actual encoding. Expects a XPath::NodeSet as first, a hashref containing +your data as second parameter. The XPath nodeset must be a node specifying a WSDL +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 + +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 + +=head1 Notes + +=head2 Why another SOAP module ? + +SOAP::Lite provides only some rudimentary WSDL support. This lack is not just +something unimplemented, but an offspring of the SOAP::Schema +class design. SOAP::Schema uses some complicated format to store XML Schema information +(mostly a big hashref, containing arrays of SOAP::Data and a SOAP::Parser-derived +object). This data structure makes it pretty hard to improve SOAP::Lite's +WSDL support. + +SOAP::WSDL uses XPath for processing WSDL. XPath is a query language standard for +XML, and usually a good choice for XML transformations or XML template processing +(and what else is WSDL-based en-/decoding ?). Besides, there's an excellent XPath +module (L) available from CPAN, and as SOAP::Lite uses XPath to +access elements in SOAP::SOM objects, this seems like a natural choice. + +Fiddling the kind of WSDL support implemented here into SOAP::Lite would mean +a larger set of changes, so I decided to build something to use as add-on. + +=head2 Memory consumption and performance + +SOAP::WSDL uses around twice the memory (or even more) SOAP::Lite uses for the +same task (but remember: SOAP::WSDL does things for you SOAP::Lite can't). +It imposes a slight delay for initialization, and for every SOAP method call, too. + +On my 1.4 GHz Pentium mobile notebook, the init delay with a simple +WSDL file (containing just one operation and some complex types and elements) +was around 50 ms, the delay for the first call around 25 ms and for subsequent +calls to the same method around 7 ms without and around 6 ms with XPath result caching +(on caching, see above). XML::XPath must do some caching, too - don't know where +else the speedup should come from. + +Calling a method of a more complex WSDL file (defining around 10 methods and +numerous complex types on around 500 lines of XML), the delay for the first +call was around 100 ms for the first and 70 ms for subsequent method calls. +wsdlinit took around 150 ms to process the stuff. With XPath result caching enabled, +all but the first call take around 35 ms. + +Using SOAP::WSDL on an idiotically complex WSDL file with just one method, but around +100 parameters for that method, mostly made up by extensions of complex types +(the heaviest XPath operation) takes around 1.2 s for the first call (0.9 with caching) +and around 830 ms for subsequent calls (arount 570 ms with caching). + +The actual performance loss compared to SOAP::Lite should be around 10 % less +than the values above - SOAP::Lite encodes the data for you, too (or you do +it yourself) - and encoding in SOAP::WSDL is already covered by the pre-call +delay time mentioned above. + +If you have lots of WebService methods and call each of them from time to time, +this delay should not affect your perfomance too much. If you have just one method +and keep calling it ever & ever again, you should cosider hardcoding your data +encoding (maybe even with hardcoded XML templates - yes, this may be a BIG speedup). + + +=head1 CAVEATS + +=head2 API change between 1.20 and 1.21 + +Giovanni S. Fois has implemented a new calling convention, which allows to specify the +port type used by SOAP::WSDL. + +While this allows greater flexibillity (and helps around the still missing bindings support), +the following lines have to be added to existing code: + + $soap->servicename( $servicename); + $soap->portname( $porttype ); + +Both lines must appear after calling + + $soap->wsdlinit(); + +=head2 API change between 1.13 and 1.14 + +The SOAP::WSDL API changed significantly between versions 1.13 and 1.14. +From 1.14 on, B expects the following arguments: method name as scalar first, +method parameters as hash following. + +The B no longer recognizes the I option - to get the same behaviour, +pass C 1> to I or call + + $soap->no_dispatch(1); + +=head2 Unstable interface + +This is alpha software - everything may (and most things will) change. +But you don't have to be afraid too much - at least the I synopsis should +be stable from 1.14 on, and that is the part you'll use most frequently. + +=head1 BUGS + +=over + +=item * Arrays of complex types are not checked for the correct number of elements + +Arrays of complex types are just encoded and not checked for correctness etc. +I don't know if I do this right yet, but output looks good. However, they are not +checked for the correct number of element (does the SOAP spec say how to +specify this ?). + +=item * +trace (and other SOAP::Lite flags) don't work + +This may be an issue with older versions of the base module (before 2.?), or with +activestate's activeperl, which do +not call the base modules I method with the flags supplied to the parent. + +There's a simple workaround: + + use SOAP::WSDL; + import SOAP::Lite +trace; + +=item * nothing else known + +But I'm sure there are some serious bugs lurking around somewhere. + +=back + +=head1 TODO + +=over + +=item Allow use of alternative XPath implementations + +XML::XPath is a great module, but it's not a race-winning one. +XML::LibXML offers a promising-looking XPath interface. SOAP::WSDL should +support both, defaulting to the faster one, and leaving the final choice +to the user. + +=back + +=head1 CHANGES + +See CHANGES file. + +=head1 COPYRIGHT + +This library is free software, you can distribute / modify it under the same +terms as perl itself. + +=head1 AUTHOR + +Martin Kutter +Giovanni S. Fois giovannisfois@tiscali.it + +=cut diff --git a/t/1_performance.t b/t/1_performance.t index d3b2909..c3fe555 100644 --- a/t/1_performance.t +++ b/t/1_performance.t @@ -1,60 +1,107 @@ -#!/usr/bin/perl -w -use strict; -use Test; -plan tests=> 6; -use Time::HiRes qw( gettimeofday tv_interval ); -use lib '../..'; -use Data::Dumper; -use Cwd; -use SOAP::WSDL; -ok 1; # if we made it this far, we're ok -### test vars END - -print "Testing SOAP::WSDL ". $SOAP::WSDL::VERSION."\n"; -print "Performance test with simple WSDL file\n"; - -my $data = { - name => 'Mein Name', - givenName => 'Vorname' - -}; - -my $dir = cwd; - -# chomp /t/ to allow running the script from t/ directory -$dir=~s|/t/?||; - -my $t0 = [gettimeofday]; -ok( my $soap=SOAP::WSDL->new( - wsdl => "file://$dir/t/acceptance/helloworld.asmx.xml", - no_dispatch => 1 -) ); -print "Create SOAP::WSDL object (".tv_interval ( $t0, [gettimeofday]) ."ms)\n" ; - -$soap->proxy('http://helloworld/helloworld.asmx'); - -$t0 = [gettimeofday]; -eval{ $soap->wsdlinit(caching => 1) }; -unless ($@) { - ok(1); -} else { - ok 0; - print STDERR $@; -} -print "wsdl file init (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ;; -$soap->readable(1); - -$t0 = [gettimeofday]; -ok( $soap->call("sayHello" , %{ $data })); -print "1 x call pre-work (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; - -$t0 = [gettimeofday]; -ok($soap->call(sayHello => %{ $data }) ); -print "1 x call pre-work (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; - -$t0 = [gettimeofday]; -for (my $i=1; $i<100; $i++) { - $soap->call(sayHello => %{ $data }); -} -ok(1); -print "100 x call pre-work (".tv_interval ( $t0, [gettimeofday]) ."s)\n"; +#!/usr/bin/perl -w +use strict; +use Test; +plan tests=> 11; +use Time::HiRes qw( gettimeofday tv_interval ); +use lib '../lib'; +use Data::Dumper; +use Cwd; + +use SOAP::WSDL; + +ok 1; # if we made it this far, we're ok +### test vars END + +print "# Testing SOAP::WSDL ". $SOAP::WSDL::VERSION."\n"; +print "# Performance test with WSDL file\n"; + +my $data = { name => 'Mein Name', + givenName => 'Vorname' }; + +my $dir = cwd; + +# chomp /t/ to allow running the script from t/ directory +$dir=~s|/t/?||; + +my $t0 = [gettimeofday]; + +{ + ok( my $soap=SOAP::WSDL->new( + wsdl => "file://$dir/t/acceptance/helloworld.asmx.xml", + no_dispatch => 1 + ) ); + + print "# Test with NO caching\n"; + print "# Create SOAP::WSDL object (".tv_interval ( $t0, [gettimeofday]) ."ms)\n" ; + + + $t0 = [gettimeofday]; + eval{ $soap->wsdlinit(caching => 0) }; + unless ($@) { + ok(1); + } else { + ok 0; + print STDERR $@; + } + print "# wsdl file init (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ;; + + $soap->readable(1); + $soap->wsdl_cache_store(); + $soap->servicename("Service1"); + $soap->portname("Service1Soap"); + + $t0 = [gettimeofday]; + ok( $soap->call("sayHello" , %{ $data })); + print "# NO cache first call: (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + + $t0 = [gettimeofday]; + ok($soap->call(sayHello => %{ $data }) ); + print "# NO cache second call (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + + $t0 = [gettimeofday]; + for (my $i=1; $i<100; $i++) { + $soap->call(sayHello => %{ $data }); + } + ok(1); + print "# NO cache: 100 x call (".tv_interval ( $t0, [gettimeofday]) ."s)\n"; +} +{ + print "# Test with caching ENABLED\n"; + + $t0 = [gettimeofday]; + ok(my $soap=SOAP::WSDL->new( + wsdl => "file://$dir/t/acceptance/helloworld.asmx.xml", + no_dispatch => 1 + ) ); + print "# Create SOAP::WSDL object (".tv_interval ( $t0, [gettimeofday]) ."ms)\n" ; + -e "$dir/t/cache" or mkdir "$dir/t/cache"; + + $t0 = [gettimeofday]; + eval{ $soap->wsdlinit(caching => 1,cache_directory =>"$dir/t/cache") }; + unless ($@) { + ok(1); + } else { + ok 0; + print STDERR $@; + } + print "# wsdl file init (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ;; + $soap->readable(1); + $soap->servicename("Service1"); + $soap->portname("Service1Soap"); + + $t0 = [gettimeofday]; + ok( $soap->call("sayHello" , %{ $data })); + print "# CACHE first call (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + + $t0 = [gettimeofday]; + ok($soap->call(sayHello => %{ $data }) ); + print "# CACHE second call: (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + + $t0 = [gettimeofday]; + for (my $i=1; $i<100; $i++) { + $soap->call(sayHello => %{ $data }); + } + ok(1); + print "# CACHE: 100 x call (".tv_interval ( $t0, [gettimeofday]) ."s)\n"; + +} diff --git a/t/2_helloworld.NET.t b/t/2_helloworld.NET.t index 3371847..6768d97 100644 --- a/t/2_helloworld.NET.t +++ b/t/2_helloworld.NET.t @@ -1,102 +1,104 @@ -#!/usr/bin/perl -w -####################################################################################### -# -# 2_helloworld.t -# -# Acceptance test for message encoding, based on .NET wsdl and example code. -# SOAP::WSDL's encoding doesn't I match the .NET example, because -# .NET doesn't always specify types (SOAP::WSDL does), and the namespace -# prefixes chosen are different (maybe the encoding style, too ? this would be a bug !) -# -######################################################################################## - -use strict; -use diagnostics; -use Test; -plan tests => 5; -use Time::HiRes qw( gettimeofday tv_interval ); -use lib '../..'; -use Cwd; -use SOAP::WSDL; -ok 1; # if we made it this far, we're ok -### test vars END -print "Testing SOAP::WSDL ". $SOAP::WSDL::VERSION."\n"; -print "Acceptance test against sample output with simple WSDL\n"; - -my $data = { - name => 'test', -# givenName => 'GIVENNAME', -# test => { -# name => 'TESTNAME', -# givenName => 'GIVENNAME', -# }, -# test1 => { -# name => 'TESTNAME', -# givenName => 'GIVENNAME', -# extend => 'EXTEND', -# }, -# test2 => { -# name => 'TESTNAME', -# givenName => 'GIVENNAME', -# } -}; - -my $t0 = [gettimeofday]; -my $dir= cwd; -$dir=~s/\/t//; -ok( my $soap=SOAP::WSDL->new( - wsdl => 'file:///'.$dir.'/t/acceptance/test.wsdl.xml', - no_dispatch => 1 -) ); -print "Create SOAP::WSDL object (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; -$soap->proxy('http://helloworld/helloworld.asmx'); -$t0 = [gettimeofday]; -ok($soap->wsdlinit()); -print "WSDL init (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; - -$t0 = [gettimeofday]; -do { - my $xml = $soap->serializer->method( $soap->call(sayHello => %{ $data }) ); - - open (FILE, "acceptance/helloworld.xml") - || open (FILE, "t/acceptance/helloworld.xml") || die "can't open acceptance file"; - my $xml_test=; - close FILE; - $xml=~s/^.+\<([^\/]+?)\:Body\>//; - $xml=~s/\<\/$1\:Body\>.*//; - - $xml_test=~s/^.+\<([^\/]+?)\:Body\>//; - $xml_test=~s/\<\/$1\:Body\>.*//; - - if ( ($xml) && ($xml eq $xml_test) ) { - ok 1; - print "Message encoding (" .tv_interval ( $t0, [gettimeofday]) ."s)\n" - } else { - ok 0; - print "Message encoding (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; - print "$xml\n$xml_test\n"; }; - }; - -$t0 = [gettimeofday]; -do { - my $xml = $soap->serializer->method( $soap->call(sayHello => %{ $data }) ); - - open (FILE, "acceptance/helloworld.xml") - || open FILE, ("t/acceptance/helloworld.xml") || die "can't open acceptance file"; - my $xml_test=; - close FILE; - $xml=~s/^.+\<([^\/]+?)\:Body\>//; - $xml=~s/\<\/$1\:Body\>.*//; - - $xml_test=~s/^.+\<([^\/]+?)\:Body\>//; - $xml_test=~s/\<\/$1\:Body\>.*//; - - if ( ($xml) && ($xml eq $xml_test) ) { - ok 1; - print "Message encoding (" .tv_interval ( $t0, [gettimeofday]) ."s)\n"; - } else { - ok 0; - print "Message encoding (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; - print "$xml\n$xml_test\n"; }; - }; - \ No newline at end of file +#!/usr/bin/perl -w +####################################################################################### +# +# 2_helloworld.t +# +# Acceptance test for message encoding, based on .NET wsdl and example code. +# SOAP::WSDL's encoding doesn't I match the .NET example, because +# .NET doesn't always specify types (SOAP::WSDL does), and the namespace +# prefixes chosen are different (maybe the encoding style, too ? this would be a bug !) +# +######################################################################################## + +use strict; +use diagnostics; +use Test; +plan tests => 5; +use Time::HiRes qw( gettimeofday tv_interval ); +use lib '../lib'; +use Cwd; +use SOAP::WSDL; +ok 1; # if we made it this far, we're ok +### test vars END +print "# Testing SOAP::WSDL ". $SOAP::WSDL::VERSION."\n"; +print "# Acceptance test against sample output with simple WSDL\n"; + +my $data = { + name => 'test', + givenName => 'GIVENNAME', + test => { + name => 'TESTNAME', + givenName => 'GIVENNAME', + }, +}; + + +my $dir= cwd; +$dir=~s/\/t\/?//; + +my $t0 = [gettimeofday]; +ok( my $soap=SOAP::WSDL->new(wsdl => 'file:///'.$dir.'/t/acceptance/test.wsdl.xml', + no_dispatch => 1 ) ); + +print "# Create SOAP::WSDL object (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + +$t0 = [gettimeofday]; +eval{ $soap->wsdlinit() }; +unless ($@) { + ok(1); +} else { + ok 0; + print STDERR $@; +} + +print "# WSDL init (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; +$soap->servicename("Service1"); +$soap->portname("Service1Soap"); + +$t0 = [gettimeofday]; +do { + my $xml = $soap->serializer->method( $soap->call(sayHello => %{ $data }) ); + + open (FILE, "acceptance/helloworld.xml") + || open (FILE, "t/acceptance/helloworld.xml") || die "can't open acceptance file"; + my $xml_test=; + close FILE; + $xml=~s/^.+\<([^\/]+?)\:Body\>//; + $xml=~s/\<\/$1\:Body\>.*//; + + $xml_test=~s/^.+\<([^\/]+?)\:Body\>//; + $xml_test=~s/\<\/$1\:Body\>.*//; + + if ( ($xml) && ($xml eq $xml_test) ) { + ok 1; + print "# Message encoding (" .tv_interval ( $t0, [gettimeofday]) ."s)\n" + } else { + ok 0; + print "# Message encoding (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + print "$xml\n$xml_test\n"; }; + }; + +$t0 = [gettimeofday]; +do { + my $xml = $soap->serializer->method( $soap->call(sayHello => %{ $data }) ); + + open (FILE, "acceptance/helloworld.xml") + || open FILE, ("t/acceptance/helloworld.xml") || die "can't open acceptance file"; + my $xml_test=; + close FILE; + $xml=~s/^.+\<([^\/]+?)\:Body\>//; + $xml=~s/\<\/$1\:Body\>.*//; + + $xml_test=~s/^.+\<([^\/]+?)\:Body\>//; + $xml_test=~s/\<\/$1\:Body\>.*//; + + if ( ($xml) && ($xml eq $xml_test) ) { + ok 1; + print "# Message encoding (" .tv_interval ( $t0, [gettimeofday]) ."s)\n"; + } else { + ok 0; + print "# Message encoding (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + print "$xml\n$xml_test\n"; + }; + }; + diff --git a/t/3_various.t b/t/3_various.t new file mode 100644 index 0000000..307b72f --- /dev/null +++ b/t/3_various.t @@ -0,0 +1,112 @@ +#!/usr/bin/perl -w +use strict; +use Test; +plan tests=> 8; +use Time::HiRes qw( gettimeofday tv_interval ); +use lib '../lib'; +use Data::Dumper; +use Cwd; +use SOAP::WSDL; + +ok 1; # if we made it this far, we're ok +### test vars END + +print "# Testing SOAP::WSDL ". $SOAP::WSDL::VERSION."\n"; +print "# Various Features Test with WSDL file \n"; + +my $data = {name => 'Mein Name', + givenName => 'Vorname'}; + +my $dir = cwd; + +# chomp /t/ to allow running the script from t/ directory +$dir=~s|/t/?||; + +my $t0 = [gettimeofday]; +ok( my $soap=SOAP::WSDL->new(wsdl => "file://$dir/t/acceptance/helloworld.asmx.xml", + no_dispatch => 1 + )); + +print "# Create SOAP::WSDL object (".tv_interval ( $t0, [gettimeofday]) ."ms)\n" ; + +$t0 = [gettimeofday]; +eval{ $soap->wsdlinit(caching => 0) }; +unless ($@) { + ok(1); +} else { + ok 0; + print STDERR $@; +} +print "# wsdl file init (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ;; + +$soap->readable(1); +$soap->servicename("Service1"); +$soap->portname("Service1Soap"); + +$t0 = [gettimeofday]; + +ok( $soap->call("sayHello" , %{ $data })); +print "# Normal Call: (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + +$soap->servicename("Service2"); +$soap->portname("Service2Soap"); + +$data = {name => 'Mein Name', + givenName => 'Vorname'}; + +$t0 = [gettimeofday]; +ok($soap->call(sayGoodBye => %{ $data }) ); +print "# Multiple Services/Port Call: (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; + +$soap->servicename("Service2"); +$soap->portname("Service2Soap"); +$data = {name => 'Mein Name', + givenName => 'Vorname', + wsdl_input_name => 'firstOverload' + }; + +$t0 = [gettimeofday]; + +my $xml = $soap->serializer->method( $soap->call(sayGoodByeOverload => %{ $data }) ); +$xml =~ / 'Mein Name', + givenName => 'Vorname', + wsdl_input_name => 'secondOverload' + }; + +$t0 = [gettimeofday]; +$xml = $soap->serializer->method( $soap->call(sayGoodByeOverload => %{ $data }) ); +$xml !~ /servicename("Service2"); +$soap->portname("Service2Soap"); +$data = { name => 'Mein Name', + 'recipients' => [ + {user_name => 'Adam', last_name => "Eden"}, + {user_name => 'Eve',last_name => 'Apple'}, + ], + wsdl_input_name => 'thirdOverload' + }; + +$t0 = [gettimeofday]; +$xml = $soap->serializer->method( $soap->call(sayGoodByeOverload => %{ $data }) ); + +my $xpath = new XML::XPath->new(xml=>$xml); + +my @recipients = $xpath->findnodes('//recipients'); +if($recipients[0]->findvalue("user_name") eq "Adam" and + $recipients[0]->findvalue("last_name") eq "Eden" and + $recipients[1]->findvalue("user_name") eq "Eve" and + $recipients[1]->findvalue("last_name") eq "Apple"){ + ok(1); +}else{ + ok(0); +} +print "# Restricted Arrays: (".tv_interval ( $t0, [gettimeofday]) ."s)\n" ; +print "#End\n"; + + diff --git a/t/97_pod.t b/t/97_pod.t new file mode 100644 index 0000000..f52784f --- /dev/null +++ b/t/97_pod.t @@ -0,0 +1,27 @@ +use Test::More; +eval "use Test::Pod 1.00"; +plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; + +use Cwd; + +my $dir = cwd; + +if ( $dir =~ /t$/ ) +{ + @directories = ('../lib/'); +} +else +{ + @directories = (); +} + +my @files = all_pod_files( + @directories +); + +plan tests => scalar(@files); + +foreach my $module (@files) +{ + pod_file_ok( $module ) +} \ No newline at end of file diff --git a/t/98_pod_coverage.t b/t/98_pod_coverage.t new file mode 100644 index 0000000..5a0fcf6 --- /dev/null +++ b/t/98_pod_coverage.t @@ -0,0 +1,27 @@ +use Test::More; +eval "use Test::Pod::Coverage 1.00"; +plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD" if $@; + + +BEGIN +{ + if (-d 't/') # we're not in the test dir - probably using + { # Build test + @dirs = ('blib/lib') + } + else + { + @dirs = '../lib'; + use lib '../lib'; # use our lib if we are in t/ (if we are, we're) + # not run from "make test" / "Build test" + } +} + +@files = all_modules( @dirs ); +plan tests => scalar @files; +foreach (@files) +{ + s/^\.\.::blib::lib:://; + s/^\.\.::lib:://; + pod_coverage_ok( $_ ); +} \ No newline at end of file diff --git a/t/acceptance/helloworld.asmx.xml b/t/acceptance/helloworld.asmx.xml index 0a18a42..2198e7e 100644 --- a/t/acceptance/helloworld.asmx.xml +++ b/t/acceptance/helloworld.asmx.xml @@ -1,75 +1,180 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/t/acceptance/helloworld.xml b/t/acceptance/helloworld.xml index 5764de6..fb328f5 100644 --- a/t/acceptance/helloworld.xml +++ b/t/acceptance/helloworld.xml @@ -1 +1 @@ -test \ No newline at end of file +testGIVENNAMETESTNAMEGIVENNAME \ No newline at end of file diff --git a/t/acceptance/test.wsdl.xml b/t/acceptance/test.wsdl.xml index 364b0a4..b9825a7 100644 --- a/t/acceptance/test.wsdl.xml +++ b/t/acceptance/test.wsdl.xml @@ -1,105 +1,105 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +