From 312f3d6bbd6d815d683bb88b6a8c0d3a765b6ae4 Mon Sep 17 00:00:00 2001 From: Martin Kutter Date: Sun, 5 Aug 2007 08:48:17 -0800 Subject: [PATCH] import SOAP-WSDL 2.00_08 from CPAN git-cpan-module: SOAP-WSDL git-cpan-version: 2.00_08 git-cpan-authorid: MKUTTER git-cpan-file: authors/id/M/MK/MKUTTER/SOAP-WSDL-2.00_08.tar.gz --- Build.PL | 12 +- MANIFEST | 9 +- META.yml | 13 +- TODO | 11 +- bin/wsdl2perl.pl | 11 +- example/lib/MyElements/string.pm | 2 +- example/lib/MyInterfaces/GlobalWeather.pm | 217 +----- example/lib/MyTypemaps/GlobalWeather.pm | 17 +- example/weather.pl | 20 +- example/weather_wsdl.pl | 25 + lib/SOAP/WSDL.pm | 666 ++++++++---------- lib/SOAP/WSDL/Base.pm | 3 +- lib/SOAP/WSDL/Client.pm | 185 +++-- lib/SOAP/WSDL/Definitions.pm | 147 ++-- lib/SOAP/WSDL/Expat/MessageParser.pm | 175 +++-- lib/SOAP/WSDL/Manual.pod | 148 +++- lib/SOAP/WSDL/Manual/Glossary.pod | 4 +- lib/SOAP/WSDL/Operation.pm | 5 +- lib/SOAP/WSDL/Parser.pod | 23 + lib/SOAP/WSDL/SAX/WSDLHandler.pm | 43 +- lib/SOAP/WSDL/TypeLookup.pm | 260 +++---- lib/SOAP/WSDL/XSD/Builtin.pm | 46 ++ lib/SOAP/WSDL/XSD/ComplexType.pm | 41 +- lib/SOAP/WSDL/XSD/Element.pm | 70 +- lib/SOAP/WSDL/XSD/Primitive.pm | 50 -- lib/SOAP/WSDL/XSD/Schema/Builtin.pm | 8 +- lib/SOAP/WSDL/XSD/SimpleType.pm | 34 +- lib/SOAP/WSDL/XSD/Typelib/Builtin.pm | 33 + lib/SOAP/WSDL/XSD/Typelib/Builtin/date.pm | 30 + lib/SOAP/WSDL/XSD/Typelib/Builtin/dateTime.pm | 31 +- lib/SOAP/WSDL/XSD/Typelib/Builtin/time.pm | 28 + lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm | 90 ++- lib/SOAP/WSDL/XSD/Typelib/Element.pm | 50 +- lib/SOAP/WSDL/XSD/Typelib/SimpleType.pm | 2 +- t/001_use.t | 2 +- t/002_sax.t | 2 +- t/006_client.t | 6 +- t/008_client_wsdl_complexType.t | 2 +- t/018_generator.t | 30 + t/098_pod.t | 22 +- t/SOAP/WSDL/02_port.t | 18 +- t/SOAP/WSDL/03_complexType-all.t | 12 +- t/SOAP/WSDL/03_complexType-sequence.t | 61 +- .../WSDL/XSD/Typelib/Builtin/002_dateTime.t | 25 + t/SOAP/WSDL/XSD/Typelib/Builtin/002_time.t | 25 + t/SOAP/WSDL/XSD/Typelib/Builtin/003_date.t | 18 + 46 files changed, 1544 insertions(+), 1188 deletions(-) create mode 100644 example/weather_wsdl.pl create mode 100644 lib/SOAP/WSDL/XSD/Builtin.pm delete mode 100644 lib/SOAP/WSDL/XSD/Primitive.pm create mode 100644 t/018_generator.t create mode 100644 t/SOAP/WSDL/XSD/Typelib/Builtin/002_dateTime.t create mode 100644 t/SOAP/WSDL/XSD/Typelib/Builtin/002_time.t create mode 100644 t/SOAP/WSDL/XSD/Typelib/Builtin/003_date.t diff --git a/Build.PL b/Build.PL index f12441c..1ed978d 100644 --- a/Build.PL +++ b/Build.PL @@ -2,30 +2,33 @@ use Module::Build; Module::Build->new( dist_abstract => 'SOAP with WSDL support', dist_name => 'SOAP-WSDL', - dist_version => '2.00_07', + dist_version => '2.00_08', module_name => 'SOAP::WSDL', license => 'artistic', requires => { 'Class::Std' => q/v0.0.8/, 'Class::Std::Storable' => 0, - 'SOAP::Lite' => 0, + 'Date::Parse' => 0, + 'Date::Format' => 0, + 'LWP::UserAgent' => 0, 'List::Util' => 0, 'File::Basename' => 0, 'File::Path' => 0, - 'XML::XPath' => 0, 'XML::LibXML' => 0, 'XML::SAX::Base' => 0, 'XML::SAX::ParserFactory' => 0, 'XML::Parser::Expat' => 0, }, buildrequires => { + 'Date::Parse' => 0, + 'Date::Format' => 0, 'Benchmark' => 0, 'Cwd' => 0, 'Test::More' => 0, - 'SOAP::Lite' => 0, 'Class::Std' => 0.0.8, 'Class::Std::Storable' => 0, 'List::Util' => 0, + 'LWP::UserAgent' => 0, 'File::Basename' => 0, 'File::Path' => 0, 'XML::Simple' => 0, @@ -34,7 +37,6 @@ Module::Build->new( 'XML::SAX::Base' => 0, 'XML::SAX::ParserFactory' => 0, 'Pod::Simple::Text' => 0, - 'XML::SAX::ParserFactory' => 0, }, recursive_test_files => 1, )->create_build_script; diff --git a/MANIFEST b/MANIFEST index 1b3a813..1e6ea1d 100644 --- a/MANIFEST +++ b/MANIFEST @@ -21,6 +21,7 @@ example/lib/MyInterfaces/GlobalWeather.pm example/lib/MyTypemaps/FullerData_x0020_Fortune_x0020_Cookie.pm example/lib/MyTypemaps/GlobalWeather.pm example/weather.pl +example/weather_wsdl.pl example/wsdl/FortuneCookie.xml example/wsdl/genericbarcode.xml example/wsdl/globalweather.xml @@ -50,9 +51,9 @@ lib/SOAP/WSDL/SOAP/Typelib/Fault11.pm lib/SOAP/WSDL/SoapOperation.pm lib/SOAP/WSDL/TypeLookup.pm lib/SOAP/WSDL/Types.pm +lib/SOAP/WSDL/XSD/Builtin.pm lib/SOAP/WSDL/XSD/ComplexType.pm lib/SOAP/WSDL/XSD/Element.pm -lib/SOAP/WSDL/XSD/Primitive.pm lib/SOAP/WSDL/XSD/Schema.pm lib/SOAP/WSDL/XSD/Schema/Builtin.pm lib/SOAP/WSDL/XSD/SimpleType.pm @@ -107,7 +108,7 @@ lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm lib/SOAP/WSDL/XSD/Typelib/Element.pm lib/SOAP/WSDL/XSD/Typelib/SimpleType.pm LICENSE -MANIFEST +MANIFEST This list of files META.yml README t/001_use.t @@ -126,6 +127,7 @@ t/014_sax_typelib.t t/015_to_typemap.t t/016_client_object.t t/017_generator.t +t/018_generator.t t/020_storable.t t/098_pod.t t/acceptance/results/03_complexType-all.xml @@ -179,4 +181,7 @@ t/SOAP/WSDL/10_performance.t t/SOAP/WSDL/11_helloworld.NET.t t/SOAP/WSDL/12_binding.pl t/SOAP/WSDL/XSD/Typelib/Builtin/001_string.t +t/SOAP/WSDL/XSD/Typelib/Builtin/002_dateTime.t +t/SOAP/WSDL/XSD/Typelib/Builtin/002_time.t +t/SOAP/WSDL/XSD/Typelib/Builtin/003_date.t TODO diff --git a/META.yml b/META.yml index eb107ca..53603c5 100644 --- a/META.yml +++ b/META.yml @@ -1,21 +1,22 @@ --- name: SOAP-WSDL -version: 2.00_07 +version: 2.00_08 author: abstract: SOAP with WSDL support license: artistic requires: Class::Std: v0.0.8 Class::Std::Storable: 0 + Date::Format: 0 + Date::Parse: 0 File::Basename: 0 File::Path: 0 + LWP::UserAgent: 0 List::Util: 0 - SOAP::Lite: 0 XML::LibXML: 0 XML::Parser::Expat: 0 XML::SAX::Base: 0 XML::SAX::ParserFactory: 0 - XML::XPath: 0 generated_by: Module::Build version 0.2808 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.2.html @@ -23,7 +24,7 @@ meta-spec: provides: SOAP::WSDL: file: lib/SOAP/WSDL.pm - version: 2.00_05 + version: 2.00_08 SOAP::WSDL::Base: file: lib/SOAP/WSDL/Base.pm SOAP::WSDL::Binding: @@ -64,12 +65,12 @@ provides: file: lib/SOAP/WSDL/TypeLookup.pm SOAP::WSDL::Types: file: lib/SOAP/WSDL/Types.pm + SOAP::WSDL::XSD::Builtin: + file: lib/SOAP/WSDL/XSD/Builtin.pm SOAP::WSDL::XSD::ComplexType: file: lib/SOAP/WSDL/XSD/ComplexType.pm SOAP::WSDL::XSD::Element: file: lib/SOAP/WSDL/XSD/Element.pm - SOAP::WSDL::XSD::Primitive: - file: lib/SOAP/WSDL/XSD/Primitive.pm SOAP::WSDL::XSD::Schema: file: lib/SOAP/WSDL/XSD/Schema.pm SOAP::WSDL::XSD::Schema::Builtin: diff --git a/TODO b/TODO index e7ecf53..bca635f 100644 --- a/TODO +++ b/TODO @@ -1,14 +1,23 @@ +- support embedded atomic types by including a second (and third and fourth) +package type_prefix::complex_type::element_name element package. +Allow unlimited depth (though this is rather wicked). +- fixup generated pod so it looks nice in SOAP::WSDL::Definitions. - remove benchmarks from tests. Create benchmark/ directory and store benchmarks in. -- SOAP::WSDL::Definitions::create creates bad interface docs. Fix it. - add tests for SOAP::WSDL::Definitions::create - test for correct creation - test against web service (fullerdata.com?) - Improve docs + - Partially DONE - Remove SOAP::Lite dependency - for now, just support HTTP(s) via LWP::UserAgent. + - Partially DONE. SOAP::WSDL still uses SOAP::Schema - write inheritance Test for all XSD::Typelib::Builtin::* classes - Check & probably fix simpleType support. The WS at http://www.webservicex.net/genericbarcode.asmx?wsdl should make up a good example for simpleType definitions. +- add default Fault11 typemap to generated typemaps + - DONE +- SOAP::WSDL::Definitions::create creates bad interface docs. Fix it. + - DONE - update all Builtin Types to new constructor BEGIN block (Class::Std unfortunately is way slow) - DONE. - Remove useless (but on CPAN annoying) doc from Builtin::* classes diff --git a/bin/wsdl2perl.pl b/bin/wsdl2perl.pl index 3388939..aee7f8f 100644 --- a/bin/wsdl2perl.pl +++ b/bin/wsdl2perl.pl @@ -5,7 +5,7 @@ use Fcntl; use IO::File; use Pod::Usage; use Getopt::Long; -use LWP::Simple qw(get); +use LWP::UserAgent; use SOAP::WSDL::SAX::WSDLHandler; use XML::LibXML; @@ -17,6 +17,7 @@ my %opt = ( typemap_prefix => 'MyTypemaps::', interface_prefix => 'MyInterfaces::', base_path => 'lib/', + proxy => undef ); GetOptions(\%opt, @@ -29,6 +30,7 @@ GetOptions(\%opt, base_path|b=s typemap_include|mi=s help|h + proxy|x=s ) ); @@ -40,7 +42,12 @@ pod2usage( -exit => 1 , verbose => 1 ) if not ($url); my $handler = SOAP::WSDL::SAX::WSDLHandler->new(); my $parser = XML::LibXML->new(); -my $xml = get($url) or die "Could not load WSDL schema $url\n"; +local $ENV{HTTP_PROXY} = $opt{proxy} if $opt{proxy}; +my $lwp = LWP::UserAgent->new(); +my $response = $lwp->get($url); +die $response->message(), "\n" if $response->code != 200; + +my $xml = $response->content(); $parser->set_handler( $handler ); $parser->parse_string( $xml ); diff --git a/example/lib/MyElements/string.pm b/example/lib/MyElements/string.pm index 097e5ff..816ce1d 100644 --- a/example/lib/MyElements/string.pm +++ b/example/lib/MyElements/string.pm @@ -13,7 +13,7 @@ use base qw( ); -sub get_xmlns { 'http://www.fullerdata.com/FortuneCookie/FortuneCookie.asmx' } +sub get_xmlns { 'http://www.webserviceX.NET' } __PACKAGE__->__set_name('string'); __PACKAGE__->__set_nillable(true); diff --git a/example/lib/MyInterfaces/GlobalWeather.pm b/example/lib/MyInterfaces/GlobalWeather.pm index 1b95456..573a060 100644 --- a/example/lib/MyInterfaces/GlobalWeather.pm +++ b/example/lib/MyInterfaces/GlobalWeather.pm @@ -38,220 +38,29 @@ http://www.webservicex.net/globalweather.asmx my $GetCitiesByCountry = $interface->GetCitiesByCountry(); -=head1 Service GlobalWeather +=head1 METHODS -=head2 Service information: +=head2 GetWeather - Port name: GlobalWeatherSoap - Binding: tns:GlobalWeatherSoap - Location: http://www.webservicex.net/globalweather.asmx +Get weather report for all major cities around the world. -=head2 SOAP Operations +SYNOPSIS: -B - - Input, output and fault messages are stated as perl hash refs. - - These are only for informational purposes - the actual implementation - normally uses object trees, not hash refs, though the input messages - may be passed to the respective methods as hash refs and will be - converted to object trees automatically. - - -=head3 GetWeather - -B - - { - 'GetWeather'=> { + $service->GetWeather({ 'CityName' => $someValue, 'CountryName' => $someValue, - }, - } + }); -B +=head2 GetCitiesByCountry - { - 'GetWeather'=> { - 'CityName' => $someValue, +Get all major cities by country name(full / part). + +SYNOPSIS: + + $service->GetCitiesByCountry({ 'CountryName' => $someValue, - }, - } - - -B - - - - -=head3 GetCitiesByCountry - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - - -=head2 Service information: - - Port name: GlobalWeatherHttpGet - Binding: tns:GlobalWeatherHttpGet - Location: - -=head2 SOAP Operations - -B - - Input, output and fault messages are stated as perl hash refs. - - These are only for informational purposes - the actual implementation - normally uses object trees, not hash refs, though the input messages - may be passed to the respective methods as hash refs and will be - converted to object trees automatically. - - -=head3 GetWeather - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - - - -=head3 GetCitiesByCountry - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - - -=head2 Service information: - - Port name: GlobalWeatherHttpPost - Binding: tns:GlobalWeatherHttpPost - Location: - -=head2 SOAP Operations - -B - - Input, output and fault messages are stated as perl hash refs. - - These are only for informational purposes - the actual implementation - normally uses object trees, not hash refs, though the input messages - may be passed to the respective methods as hash refs and will be - converted to object trees automatically. - - -=head3 GetWeather - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - - - -=head3 GetCitiesByCountry - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - { - 'GetWeather'=> { - 'CityName' => $someValue, - 'CountryName' => $someValue, - }, - } - - -B - - + }); diff --git a/example/lib/MyTypemaps/GlobalWeather.pm b/example/lib/MyTypemaps/GlobalWeather.pm index 1bb6dbf..888380e 100644 --- a/example/lib/MyTypemaps/GlobalWeather.pm +++ b/example/lib/MyTypemaps/GlobalWeather.pm @@ -3,6 +3,14 @@ use strict; use warnings; my %typemap = ( +# SOAP 1.1 fault typemap +'Fault' => 'SOAP::WSDL::SOAP::Typelib::Fault11', +'Fault/faultcode' => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', +'Fault/faultactor' => 'SOAP::WSDL::XSD::Typelib::Builtin::TOKEN', +'Fault/faultstring' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', +'Fault/detail' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', + +# generated typemap 'GetWeather' => 'MyElements::GetWeather', # atomic complex type (sequence) 'GetWeather/CityName' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', @@ -24,12 +32,9 @@ my %typemap = ( 'GetCitiesByCountryResponse/GetCitiesByCountryResult' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', # end atomic complex type (sequence) -Fault => 'SOAP::WSDL::SOAP::Typelib::Fault11', -'Fault/faultstring' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', -'Fault/faultcode' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', -'Fault/faultactor' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', -'Fault/faultactor' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', -'Fault/detail' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', + + + ); diff --git a/example/weather.pl b/example/weather.pl index 43650bb..69bf27c 100644 --- a/example/weather.pl +++ b/example/weather.pl @@ -1,19 +1,19 @@ -# Accessing the fortune cookie service at +# Accessing the globalweather service at # www.webservicex.net/GlobalWeather/GlobalWeather.asmx # +# Note that the GlobalWeather web service returns a (quoted) XML structure - +# don't be surprised by the response's format. +# # I have no connection to www.webservicex.net -# # Use this script at your own risk. +# +# This script demonstrates the use of a interface generated by wsdl2perl.pl use lib 'lib/'; use MyInterfaces::GlobalWeather; -my $webservice = MyInterfaces::GlobalWeather->new(); +my $weather = MyInterfaces::GlobalWeather->new(); +my $result = $weather->GetWeather({ CountryName => 'Germany', CityName => 'Munich' }); -my $result = $webservice->GetWeather({ CountryName => 'Germany', CityName => 'Munich' }); +die $result->get_faultstring()->get_value() if not ($result); # boolean comparison overloaded -if ($result) { - print $result->get_GetWeatherResult()->get_value(), "\n"; -} -else { - print $result; -} +print $result->get_GetWeatherResult()->get_value() "\n"; diff --git a/example/weather_wsdl.pl b/example/weather_wsdl.pl new file mode 100644 index 0000000..3e3dd12 --- /dev/null +++ b/example/weather_wsdl.pl @@ -0,0 +1,25 @@ +# Accessing the globalweather service at +# www.webservicex.net/GlobalWeather/GlobalWeather.asmx +# +# Note that the GlobalWeather web service returns a (quoted) XML structure - +# don't be surprised by the response's format. +# +# I have no connection to www.webservicex.net +# Use this script at your own risk. +# +# This script demonstrates the use of SOAP::WSDL in SOAP::Lite style. + +use lib 'lib/'; +use lib '../lib'; +use SOAP::WSDL; +use File::Basename qw(dirname); +use File::Spec; +my $path = File::Spec->rel2abs( dirname __FILE__); + +my $soap = SOAP::WSDL->new(); +my $som = $soap->wsdl("file:///$path/wsdl/globalweather.xml") + ->call('GetWeather', GetWeather => { CountryName => 'Germany', CityName => 'Munich' }); + +die $som->message() if $som->fault(); + +print $som->result(); diff --git a/lib/SOAP/WSDL.pm b/lib/SOAP/WSDL.pm index 39e5c02..5569584 100644 --- a/lib/SOAP/WSDL.pm +++ b/lib/SOAP/WSDL.pm @@ -1,181 +1,188 @@ package SOAP::WSDL; use strict; use warnings; -use vars qw/$AUTOLOAD/; +use vars qw($AUTOLOAD); +use Carp; use Scalar::Util qw(blessed); +use SOAP::WSDL::Client; use SOAP::WSDL::Envelope; use SOAP::WSDL::SAX::WSDLHandler; -use base qw(SOAP::Lite); +use Class::Std; +use XML::LibXML; + use Data::Dumper; +use LWP::UserAgent; +our $VERSION='2.00_08'; -our $VERSION='2.00_05'; - -BEGIN { - eval { - use XML::LibXML; - }; - if ($@) { - use XML::SAX::ParserFactory; - } -} +my %no_dispatch_of :ATTR(:name); +my %wsdl_of :ATTR(:name); +my %proxy_of :ATTR(:name); +my %readable_of :ATTR(:name); +my %autotype_of :ATTR(:name); +my %outputxml_of :ATTR(:name); +my %outputtree_of :ATTR(:name); +my %outputhash_of :ATTR(:name); +my %servicename_of :ATTR(:name); +my %portname_of :ATTR(:name); +my %class_resolver_of :ATTR(:name); -sub AUTOLOAD { - my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::') + 2); - die "$method not found"; +my %method_info_of :ATTR(:default<()>); +my %port_of :ATTR(:default<()>); +my %porttype_of :ATTR(:default<()>); +my %binding_of :ATTR(:default<()>); +my %service_of :ATTR(:default<()>); +my %definitions_of :ATTR(:get :default<()>); +my %serialize_options_of :ATTR(:default<()>); +my %explain_options_of :ATTR(:default<()>); + +my %client_of :ATTR(:name :default<()>); + +my %LOOKUP = ( + no_dispatch => \%no_dispatch_of, + class_resolver => \%class_resolver_of, + wsdl => \%wsdl_of, + proxy => \%proxy_of, + readable => \%readable_of, + autotype => \%autotype_of, + outputxml => \%outputxml_of, + outputtree => \%outputtree_of, + outputhash => \%outputhash_of, + portname => \%portname_of, + servicename => \%servicename_of, +); + +for my $method (keys %LOOKUP ) { + no strict qw(refs); + *{ $method } = sub { + my $self = shift; + my $ident = ident $self; + if (@_) { + $LOOKUP{ $method }->{ $ident } = shift; + return $self; + } + return $LOOKUP{ $method }->{ $ident }; + }; } -sub outputtree { - my $self = shift; - return $self->{ _WSDL }->{ outputtree } if not @_; - return $self->{ _WSDL }->{ outputtree } = shift; -} - -sub class_resolver { - my $self = shift; - return $self->{ _WSDL }->{ class_resolver } if not @_; - return $self->{ _WSDL }->{ class_resolver } = shift; -} + +{ + no warnings qw(redefine); + sub new { + my $class = shift; + my %args_from = @_; + my $self = \do { my $foo = undef }; + bless $self, $class; + + for (keys %args_from) { + my $method = $self->can("set_$_") + or croak "unknown parameter $_ passed to new"; + $method->($self, $args_from{$_}); + } + my $ident = ident $self; + $self->wsdlinit() if ($wsdl_of{ $ident }); + + $client_of{ $ident } = SOAP::WSDL::Client->new(); + + return $self; + } + +} sub wsdlinit { - my $self = shift; + my $self = shift; + my $ident = ident $self; my %opt = @_; - - my $wsdl_xml = SOAP::Schema->new( schema_url => $self->wsdl() )->access( - $self->wsdl() - ); - - my $filter; - my $parser = eval { XML::LibXML->new() }; - if ($parser) { - $filter = SOAP::WSDL::SAX::WSDLHandler->new(); - $parser->set_handler( $filter ); - } - else { - $filter = SOAP::WSDL::SAX::WSDLHandler->new( base => 'XML::SAX::Base' ); - $parser = XML::SAX::ParserFactory->parser( Handler => $filter ); - } - - $parser->parse_string( $wsdl_xml ); - - my $wsdl_definitions = $filter->get_data() - or die "unable to parse WSDL"; - + + my $lwp = LWP::UserAgent->new(); + my $response = $lwp->get( $wsdl_of{ $ident } ); + croak $response->message() if ($response->code != 200); + + # TODO: Port parser to expat and remove XML::LibXML dependency + my $parser = XML::LibXML->new(); + my $filter = SOAP::WSDL::SAX::WSDLHandler->new(); + $parser->set_handler( $filter ); + $parser->parse_string( $response->content() ); + + # sanity checks + my $wsdl_definitions = $filter->get_data() or die "unable to parse WSDL"; my $types = $wsdl_definitions->first_types() or die "unable to extract schema from WSDL"; - my $ns = $wsdl_definitions->get_xmlns() or die "unable to extract XML Namespaces" . $wsdl_definitions->to_string; ( %{ $ns } ) or die "unable to extract XML Namespaces"; # setup lookup variables - $self->{ _WSDL }->{ wsdl_definitions } = $wsdl_definitions; - $self->{ _WSDL }->{ serialize_options } = { + $definitions_of{ $ident } = $wsdl_definitions; + $serialize_options_of{ $ident } = { autotype => 0, - readable => $self->readable(), typelib => $types, namespace => $ns, }; - $self->{ _WSDL }->{ explain_options } = { + $explain_options_of{ $ident } = { readable => $self->readable(), wsdl => $wsdl_definitions, namespace => $ns, typelib => $types, }; - $self->servicename($opt{servicename}) if $opt{servicename}; - $self->portname($opt{portname}) if $opt{portname}; + $servicename_of{ $ident } = $opt{servicename} if $opt{servicename}; + $portname_of{ $ident } = $opt{portname} if $opt{portname}; return $self; } ## end sub wsdlinit -sub _wsdl_get_service { - my $self = shift; - my $service; - my $wsdl = $self->{ _WSDL }->{ wsdl_definitions }; - my $ns = $wsdl->get_targetNamespace(); - if ( $self->{ _WSDL }->{ servicename } ) - { - $service = - $wsdl->find_service( $ns, $self->{ _WSDL }->{ servicename } ); - } - else - { - $service = $wsdl->get_service()->[ 0 ]; - warn "no servicename specified - using " . $service->get_name(); - } - return $self->{ _WSDL }->{ service } = $service; +sub _wsdl_get_service :PRIVATE { + my $ident = ident shift; + my $wsdl = $definitions_of{ $ident }; + return $service_of{ $ident } = $servicename_of{ $ident } + ? $wsdl->find_service( $wsdl->get_targetNamespace() , $servicename_of{ $ident } ) + : $service_of{ $ident } = $wsdl->get_service()->[ 0 ]; } ## end sub _wsdl_get_service -sub _wsdl_get_port { - my $self = shift; - my $service = $self->{ _WSDL }->{ service } - || $self->_wsdl_get_service(); - my $wsdl = $self->{ _WSDL }->{ wsdl_definitions }; - my $ns = $wsdl->get_targetNamespace(); - my $port; - if ( $self->{ _WSDL }->{ portname } ) - { - $port = $service->get_port( $ns, $self->{ _WSDL }->{ portname } ); - } - else - { - $port = $service->get_port()->[ 0 ]; - } - $self->{ _WSDL }->{ port } = $port; - - # preload portType - $self->_wsdl_get_portType(); - - # Auto-set proxy - required before issuing call() - $self->proxy( $port->get_location() ); - - return $port; +sub _wsdl_get_port :PRIVATE { + my $ident = ident shift; + my $wsdl = $definitions_of{ $ident }; + my $ns = $wsdl->get_targetNamespace(); + return $port_of{ $ident } = $portname_of{ $ident } + ? $service_of{ $ident }->get_port( $ns, $portname_of{ $ident } ) + : $port_of{ $ident } = $service_of{ $ident }->get_port()->[ 0 ]; } ## end sub _wsdl_get_port -sub _wsdl_get_binding { - my $self = shift; - my $wsdl = $self->{ _WSDL }->{ wsdl_definitions }; - my $ns = $wsdl->get_targetNamespace(); - my $port = $self->{ _WSDL }->{ port } - || $self->_wsdl_get_port(); - - my ( $prefix, $localname ) = split /:/, $port->get_binding(); - - # TODO lookup $ns instead of just using - # the top element's targetns... - my $binding = $wsdl->find_binding( $ns, $localname ) +sub _wsdl_get_binding :PRIVATE { + my $self = shift; + my $ident = ident $self; + my $wsdl = $definitions_of{ $ident }; + my $port = $port_of{ $ident } || $self->_wsdl_get_port(); + $binding_of{ $ident } = $wsdl->find_binding( $wsdl->_expand( $port->get_binding() ) ) or die "no binding found for ", $port->get_binding(); - return $self->{ _WSDL }->{ binding } = $binding; + return $binding_of{ $ident }; } ## end sub _wsdl_get_binding -sub _wsdl_get_portType { +sub _wsdl_get_portType :PRIVATE { my $self = shift; - my $wsdl = $self->{ _WSDL }->{ wsdl_definitions }; - my $binding = $self->{ _WSDL }->{ binding } - || $self->_wsdl_get_binding(); - my $ns = $wsdl->get_targetNamespace(); - my ( $prefix, $localname ) = split /:/, $binding->get_type(); - my $portType = $wsdl->find_portType( $ns, $localname ); - $self->{ _WSDL }->{ portType } = $portType; - return $portType; + my $ident = ident $self; + my $wsdl = $definitions_of{ $ident }; + my $binding = $binding_of{ $ident } || $self->_wsdl_get_binding(); + $porttype_of{ $ident } = $wsdl->find_portType( $wsdl->_expand( $binding->get_type() ) ) + or die "cannot find portType for " . $binding->get_type(); + return $porttype_of{ $ident }; } ## end sub _wsdl_get_portType -sub _wsdl_init_methods { - my $self = shift; - my $wsdl = $self->{ _WSDL }->{ wsdl_definitions }; +sub _wsdl_init_methods :PRIVATE { + my $self = shift; + my $ident = ident $self; + my $wsdl = $definitions_of{ $ident }; my $ns = $wsdl->get_targetNamespace(); # get bindings, portType, message, part(s) # - use cached values where possible for speed, # private methods if not for clear separation... - my $binding = $self->{ _WSDL }->{ binding } - || $self->_wsdl_get_binding() + $self->_wsdl_get_service if not ($service_of{ $ident }); + my $binding = $binding_of{ $ident } || $self->_wsdl_get_binding() || die "Can't find binding"; - my $portType = $self->{ _WSDL }->{ portType } - || $self->_wsdl_get_portType() - || die "Can't find portType"; + my $portType = $porttype_of{ $ident } || $self->_wsdl_get_portType(); - my $methodHashRef = {}; + $method_info_of{ $ident } = {}; foreach my $binding_operation (@{ $binding->get_operation() }) { @@ -211,201 +218,60 @@ sub _wsdl_init_methods { } : undef; - $methodHashRef->{ $binding_operation->get_name() } = $method; + $method_info_of{ $ident }->{ $binding_operation->get_name() } = $method; } - - $self->{ _WSDL }->{ methodInfo } = $methodHashRef; - - return $methodHashRef; + + return $method_info_of{ $ident }; } - + sub call { - my $self = shift; + my $self = shift; + my $ident = ident $self; my $method = shift; my $data = ref $_[0] ? $_[0] : { @_ }; - - my $content = q{}; - my $envelope; - my $methodInfo; - - if (blessed $data - && $data->isa('SOAP::WSDL::XSD::Typelib::Builtin::anyType')) - { - $envelope = SOAP::WSDL::Envelope->serialize( $method, $data ); - - # TODO replace by something derived from binding - this is just a - # workaround... - $methodInfo->{ soap_action } - = join '/', $data->get_xmlns(), $method; - } - else { - my $methodLookup = $self->{ _WSDL }->{ methodInfo } - || $self->_wsdl_init_methods(); - - $methodInfo = $methodLookup->{ $method }; - my $partListRef = $methodInfo->{ parts }; - - # set serializer options - # TODO allow custom options here - my $opt = $self->{ _WSDL }->{ serialize_options }; - - # set response target namespace - # TODO make rpc-encoded encoding recognise this namespace - # $opt->{ targetNamespace } = $soap_binding_operation ? - # $operation->input()->namespace() : undef; - - # serialize content - # TODO create surrounding element for rpc-encoded messages - foreach my $part ( @{ $partListRef } ) { - $content .= $part->serialize( $method, $data, $opt ); - } - $envelope = SOAP::WSDL::Envelope->serialize( $method, $content , $opt ); - }; - - - if ( $self->no_dispatch() ) { - return $envelope; - } ## end if ( $self->no_dispatch... - - # get response via transport layer - # TODO remove dependency from SOAP::Lite and use a - # SAX-based filter using XML::LibXML to get the - # result. - # Filter should have the following methods: - # - result: returns the result of the call (like SOAP::Lite, but as - # perl data structure) - # - header: returns the content of the SOAP header - # - fault: returns the result of the call if a SOAP fault is sent back - # by the server. Retuns undef (nothing) if the call has been - # processed without errors. - my $response = $self->transport->send_receive( - context => $self, # this is provided for context - endpoint => $self->endpoint(), - action => $methodInfo->{ soap_action }, # SOAPAction from binding - envelope => $envelope, # use custom content - ); - - return $response if ($self->outputxml() ); - - if ($self->outputtree()) { - - my ($parser, $handler); # replace by globals - singleton is faster - if (not $parser) { - require SOAP::WSDL::SOAP::Typelib::Fault11; - require SOAP::WSDL::SAX::MessageHandler; - require XML::LibXML; - $handler = SOAP::WSDL::SAX::MessageHandler->new( - { class_resolver => $self->class_resolver() }, - ); - $parser = XML::LibXML->new(); - $parser->set_handler( $handler); - } - - # if we had no success (Transport layer error status code) - # or if transport layer failed - if (! $self->transport->is_success() ) { - # Try deserializing response - there may be some - if ($response) { - eval { $parser->parse_string( $response ) }; - return $handler->get_data if not $@; - }; - - # generate & return fault if we cannot serialize response - # or have none... - return SOAP::WSDL::SOAP::Typelib::Fault11->new({ - faultcode => 'soap:Server', - faultactor => 'urn:localhost', - faultstring => 'Error sending / receiving message: ' - . $self->transport->message() - }); - } - - eval { $parser->parse_string( $response ) }; - - # return fault if we cannot deserialize response - if ($@) { - return SOAP::WSDL::SOAP::Typelib::Fault11->new({ - faultcode => 'soap:Server', - faultactor => 'urn:localhost', - faultstring => "Error deserializing message: $@. \n" - . "Message was: \n$response" - }); - } - - return $handler->get_data(); - } - - # deserialize and store result - my $result = $self->{ '_call' } = - eval { $self->deserializer->deserialize( $response ) } - if $response; - - if ( - !$self->transport->is_success || # transport fault - $@ || # not deserializible - # fault message even if transport OK - # or no transport error (for example, fo TCP, POP3, IO implementations) - UNIVERSAL::isa( $result => 'SOAP::SOM' ) && $result->fault - ) { - return $self->{ '_call' } = ( - $self->on_fault->( $self, ($@) - ? $@ . ( $response || '' ) - : $result - ) || $result - ); - } ## end if ( !$self->transport... - - return unless $response; # nothing to do for one-ways - return $result; -} ## end sub call + + $self->wsdlinit() if not ($definitions_of{ $ident }); + $self->_wsdl_init_methods() if not ($method_info_of{ $ident }); + + my $client = $client_of{ $ident }; + $client->set_proxy( $proxy_of{ $ident } || $port_of{ $ident }->get_location() ); + $client->set_no_dispatch( $no_dispatch_of{ $ident } ); + $client->set_outputxml( $outputtree_of{ $ident } ? 0 : 1 ); + + my $response = (blessed $data) + ? $client->call( $method, $data ) + : do { + + my $content = ''; + # TODO support RPC-encoding: Top-Level element + namespace... + foreach my $part ( @{ $method_info_of{ $ident }->{ $method }->{ parts } } ) { + $client->set_on_action( sub { $part->get_targetNamespace() . '/' . $_[1] } ); + $content .= $part->serialize( $method, $data, + { + %{ $serialize_options_of{ $ident } }, + readable => $readable_of{ $ident }, + } ); + } + $client->call($method, $content); + }; + + return $response if ( + $outputxml_of{ $ident } +# || $outputhash_of{ $ident } + || $outputtree_of{ $ident } + || $no_dispatch_of{ $ident } ); + + return unless $response; # nothing to do for one-ways + # now convert into SOAP::SOM - bah ! + require SOAP::Lite; + return SOAP::Deserializer->new()->deserialize( $response ); +} sub explain { - my $self = shift; - my $opt = $self->{ _WSDL }->{ explain_options }; - - return $self->{ _WSDL }->{ wsdl_definitions }->explain( $opt ); -} ## end sub explain - -sub _load_method { - my $method = shift; - no strict "refs"; - *$method = sub { - my $self = shift; - return ( @_ ) ? $self->{ _WSDL }->{ $method } = shift - : $self->{ _WSDL }->{ $method } - }; -} ## end sub _load_method - -&_load_method( 'no_dispatch' ); -&_load_method( 'wsdl' ); - -sub servicename { - my $self = shift; - return $self->{ _WSDL }->{ servicename } if ( not @_ ); - $self->{ _WSDL }->{ servicename } = shift; - - my $ns = $self->{ _WSDL }->{ wsdl_definitions }->get_targetNamespace(); - - $self->{ _WSDL }->{ service } = - $self->{ _WSDL }->{ wsdl_definitions } - ->find_service( $ns, $self->{ _WSDL }->{ servicename } ) - or die "No such service: " . $self->{ _WSDL }->{ servicename }; - return $self; -} ## end sub servicename - -sub portname { - my $self = shift; - return $self->{ _WSDL }->{ portname } if ( not @_ ); - $self->{ _WSDL }->{ portname } = shift; - - my $ns = $self->{ _WSDL }->{ wsdl_definitions }->get_targetNamespace(); - - $self->{ _WSDL }->{ port } = - $self->{ _WSDL }->{ service } - ->find_port( $ns, $self->{ _WSDL }->{ portname } ) - or die "No such port: " . $self->{ _WSDL }->{ portname }; - return $self; -} ## end sub portname + my $ident = ident shift; + my $opt = $explain_options_of{ $ident }; + return $definitions_of{ $ident }->explain( $opt ); +} 1; @@ -416,32 +282,53 @@ __END__ =head1 NAME SOAP::WSDL - SOAP with WSDL support + +=head1 Overview + +For creating Perl classes instrumenting a web service with a WSDL definition, +read L. + +For using an interpreting (thus slow and somewhat troublesome) WSDL based +SOAP client, which mimics L's API, read on. =head1 SYNOPSIS my $soap = SOAP::WSDL->new( wsdl => 'file://bla.wsdl', readable => 1, - )->wsdlinit(); + ); my $result = $soap->call('MyMethod', %data); =head1 DESCRIPTION -SOAP::WSDL provides easy access to Web Services with WSDL descriptions. - +SOAP::WSDL provides easy access to Web Services with WSDL descriptions. + The WSDL is parsed and stored in memory. -Your data is serialized according to the rules in the WSDL and sent via -SOAP::Lite's transport mechanism. +Your data is serialized according to the rules in the WSDL. + +The only transport mechanisms currently supported are http and https. =head1 METHODS + +=head2 new + +Constructor. All parameters passed are passed to the corresponding methods. + +=head2 call + +Performs a SOAP call. The result is either an object tree (with outputtree), +a hash reference (with outputhash), plain XML (with outputxml) or a SOAP::SOM +object (with neither of the above set). + + my $result = $soap->call('method', %data); =head2 wsdlinit Reads the WSDL file and initializes SOAP::WSDL for working with it. -Must be called after the wsdl URL has been set, and before calling one of +Is called automatically from call() if not called directly before. servicename portname @@ -455,14 +342,6 @@ wsdlinit: portname => 'MyPort' ); -=head2 call - -Performs a SOAP call. The result is either an object tree (with outputtree), -a hash reference (with outputhash), plain XML (with outputxml) or a SOAP::SOM -object (with neither of the above set). - - my $result = $soap->call('method', %data); - =head1 CONFIGURATION METHODS =head2 outputtree @@ -515,8 +394,6 @@ SOAP::WSDL::XSD::ComplexType on how to build / generate one. Sets the service to operate on. If no service is set via servicename, the first service found is used. -Must be called after calling wsdlinit(). - Returns the soap object, so you can chain calls like $soap->servicename->('Name')->portname('Port'); @@ -528,8 +405,6 @@ Returns the soap object, so you can chain calls like Sets the port to operate on. If no port is set via portname, the first port found is used. -Must be called after calling wsdlinit(). - Returns the soap object, so you can chain calls like $soap->portname('Port')->call('MyMethod', %data); @@ -539,12 +414,25 @@ Returns the soap object, so you can chain calls like When set, call() returns the plain request XML instead of dispatching the SOAP call to the SOAP service. Handy for testing/debugging. -=head2 _wsdl_init_methods - -Creates a lookup table containing the information required for all methods -specified for the service/port selected. - -The lookup table is used by L. +=head1 ACCESS TO SOAP::WSDL's internals + +=head2 get_client / set_client + +Returns the SOAP client implementation used (normally a SOAP::WSDL::Client +object). + +Useful for enabling tracing: + + # enable tracing via 'warn' + $soap->get_client->set_trace(1); + + # enable tracing via a custom facility - + # Log::Log4perl in this case... + $soap->get_client->set_trace(sub { Log::Log4perl->get_logger->info(@_) } ); + +=head1 EXAMPLES + +See the examples/ directory. =head1 Differences to previous versions @@ -560,7 +448,8 @@ it's content. The object tree has two main functions: It knows how to serialize data passed as hash ref, and how to render the WSDL elements found into perl classes. -Yup your're right, there's a builting code generation facility. +Yup your're right, there's a builting code generation facility. Read +L for using it. =item * no_dispatch @@ -581,33 +470,80 @@ You may pass the servicename and portname as attributes to wsdlinit, though. =back =head1 Differences to SOAP::Lite + +=head3 Output formats + +In contrast to SOAP::Lite, SOAP::WSDL supports the following output formats: + +=over + +=item * SOAP::SOM objects. + +This is the default. SOAP::Lite is required for outputting SOAP::SOM objects. + +=item * Object trees. + +This is the recommended output format. +You need a class resolver (typemap) for outputting object trees. +See L above. + +=item * Hash refs + +This is for convnience: A single hash ref containing the content of the +SOAP body. + +=item * xml + +See below. + +=back + +=head3 outputxml -=head2 Auto-Dispatching +SOAP::Lite returns only the content of the SOAP body when outputxml is set +to true. SOAP::WSDL returns the complete XML response. + +=head3 Auto-Dispatching SOAP::WSDL does B support auto-dispatching. This is on purpose: You may easily create interface classes by using -SOAP::WSDL and implementing something like +SOAP::WSDL::Client and implementing something like sub mySoapMethod { my $self = shift; $soap_wsdl_client->call( mySoapMethod, @_); } -You may even do this in a class factory - SOAP::WSDL provides the methods -for generating such interfaces. - -SOAP::Lite's autodispatching mechanism is - though convenient - a constant -source of errors: Every typo in a method name gets caught by AUTOLOAD and -may lead to unpredictable results. +You may even do this in a class factory - see L for creating +such interfaces. + +=head3 Debugging / Tracing + +While SOAP::Lite features a global tracing facility, SOAP::WSDL +allows to switch tracing on/of on a per-object base. + +This has to be done in the SOAP client used by SOAP::WSDL - see +L for an example and L for +details. =head1 Bugs and Limitations =over - -=item * readable - -readable() must be called before calling wsdlinit. This is a bug. + +=item * SOAP Headers are not supported + +There's no way to use SOAP Headers with SOAP::WSDL yet. + +=item * Apache SOAP datatypes are not supported + +You currently can't use SOAP::WSDL with Apache SOAP datatypes like map. + +If you want this changed, email me a copy of the specs, please. + +=item * outputhash + +outputhash is not implemented yet. =item * Unsupported XML Schema definitions @@ -619,7 +555,7 @@ The following XML Schema definitions are not supported: simpleContent complexContent -=item * Serialization of hash refs dos not work for ambiguous values +=item * Serialization of hash refs dos not work for ambiguos values If you have list elements with multiple occurences allowed, SOAP::WSDL has no means of finding out which variant you meant. @@ -629,11 +565,11 @@ Passing in item => [1,2,3] could serialize to 1 23 12 3 -Ambiguos data can be avoided by passing an object tree as data. +Ambiguos data can be avoided by passing an objects as data. =item * XML Schema facets -Almost all XML schema facets are not yet implemented. The only facets +Almost no XML schema facets are implemented yet. The only facets currently implemented are: fixed @@ -651,7 +587,25 @@ The following facets have no influence yet: enumeration =back - + +=head1 ACKNOWLEDGMENTS + +There are many people out there who fostered SOAP::WSDL's developement. +I would like to thank them all (and apologize to all those I have forgotten). + +Giovanni S. Fois wrote a improved version of SOAP::WSDL (which eventually became v1.23) + +Damian A. Martinez Gelabert, Dennis S. Hennen, Dan Horne, Peter Orvos, Marc Overmeer, +Jon Robens, Isidro Vila Verde and Glenn Wood spotted bugs and/or +suggested improvements in the 1.2x releases. + +Andreas 'ACID' Specht constantly asked for better performance. + +Numerous people sent me their real-world WSDL files for testing. Thank you. + +Paul Kulchenko and Byrne Reese wrote and maintained SOAP::Lite and thus provided a +base (and counterpart) for SOAP::WSDL. + =head1 LICENSE Copyright 2004-2007 Martin Kutter. @@ -661,7 +615,7 @@ the same terms as perl itself =head1 AUTHOR -Martin Kutter Emartin.kutter fen-net.deE + Martin Kutter Emartin.kutter fen-net.deE =cut diff --git a/lib/SOAP/WSDL/Base.pm b/lib/SOAP/WSDL/Base.pm index eb68107..5aceecc 100644 --- a/lib/SOAP/WSDL/Base.pm +++ b/lib/SOAP/WSDL/Base.pm @@ -6,7 +6,8 @@ use Class::Std::Storable; use List::Util qw(first); my %id_of :ATTR(:name :default<()>); -my %name_of :ATTR(:name :default<()>); +my %name_of :ATTR(:name :default<()>); +my %documentation_of :ATTR(:name :default<()>); my %targetNamespace_of :ATTR(:name :default<()>); my %xmlns_of :ATTR(:name :default<{}>); my %parent_of :ATTR(:name :default<()>); diff --git a/lib/SOAP/WSDL/Client.pm b/lib/SOAP/WSDL/Client.pm index fe80bf0..55b6856 100644 --- a/lib/SOAP/WSDL/Client.pm +++ b/lib/SOAP/WSDL/Client.pm @@ -2,26 +2,45 @@ package SOAP::WSDL::Client; use strict; use warnings; use Carp; -use Scalar::Util qw(blessed); -use SOAP::WSDL::Envelope; -use SOAP::Lite; + use Class::Std::Storable; +use LWP::UserAgent; +use HTTP::Request; +use Scalar::Util qw(blessed); + +use SOAP::WSDL::Envelope; use SOAP::WSDL::Expat::MessageParser; use SOAP::WSDL::SOAP::Typelib::Fault11; # Package globals for speed... my $PARSER; -my $MESSAGE_HANDLER; - + my %class_resolver_of :ATTR(:name :default<()>); my %no_dispatch_of :ATTR(:name :default<()>); my %outputxml_of :ATTR(:name :default<()>); my %proxy_of :ATTR(:name :default<()>); + +my %fault_class_of :ATTR(:name :default); +my %trace_of :ATTR(:set :init_arg :default<()> ); +my %on_action_of :ATTR(:name :default<()>); +my %content_type_of :ATTR(:name :default); +#/#trick editors + # TODO remove when preparing 2.01 sub outputtree { warn 'outputtree is deprecated and' . 'will be removed before reaching v2.01 !' } - + +sub get_trace { + my $ident = ident $_[0]; + return $trace_of{ $ident } + ? ref $trace_of{ $ident } eq 'CODE' + ? $trace_of{ $ident } + : sub { warn @_ } + : () +} + +# Mimic SOAP::Lite's behaviour for getter/setter routines SUBFACTORY: { no strict qw(refs); for (qw(class_resolver no_dispatch outputxml proxy)) { @@ -43,83 +62,84 @@ BEGIN { sub call { my $self = shift; - my $method = shift; - my $data = ref $_[0] ? $_[0] : { @_ }; - my $content = q{}; - my ($envelope, $soap_action); + my $method = shift; + my $ident = ident $self; + my $data = ref $_[0] + ? $_[0] + : (@_>1) + ? { @_ } + : $_[0]; + + my $soap_action; + my $trace_sub = $self->get_trace(); + my $envelope = SOAP::WSDL::Envelope->serialize( $method, $data ); if (blessed $data && $data->isa('SOAP::WSDL::XSD::Typelib::Builtin::anyType')) { - $envelope = SOAP::WSDL::Envelope->serialize( $method, $data ); # TODO replace by something derived from binding - this is just a # workaround... $soap_action = join '/', $data->get_xmlns(), $method; } else { $envelope = SOAP::WSDL::Envelope->serialize( $method, $data ); - # $soap_action = $self->on_action( $method ); - } - + # TODO add something like SOAP::Lite's on_action mechanism + $soap_action = $on_action_of{$ident}->( $self, $method ) if ($on_action_of{$ident}); + } return $envelope if $self->no_dispatch(); - - # warn $envelope; # get response via transport layer - # TODO remove dependency from SOAP::Lite and use a - # SAX-based filter using XML::LibXML to get the - # result. - # Filter should have the following methods: + # maybe we should return a result with the following methods: # - result: returns the result of the call (like SOAP::Lite, but as - # perl data structure) + # object tree) # - header: returns the content of the SOAP header # - fault: returns the result of the call if a SOAP fault is sent back # by the server. Retuns undef (nothing) if the call has been # processed without errors. - my $soap = SOAP::Lite->new()->proxy( $self->get_proxy() ); - my $response = $soap->transport->send_receive( - context => $self, # this is provided for context - endpoint => $soap->endpoint(), - action => $soap_action, # SOAPAction, should be from binding - envelope => $envelope, # use custom content - ); - - # warn 'Received ' . length($response) . ' bytes of content'; - return $response if ($self->outputxml() ); + my $ua = LWP::UserAgent->new(); + my $request = HTTP::Request->new( 'POST', + $self->get_proxy(), + [ 'Content-Type', $content_type_of{ $ident }, + 'Content-Length', length($envelope), + 'SOAPAction', $soap_action, + ], + $envelope ); + + + $trace_sub->( $request->as_string() ) if $trace_of{ $ident }; # if for speed + my $response = $ua->request( $request ); + $trace_sub->( $response->as_string() ) if $trace_of{ $ident }; # if for speed + + return $response->content() if ($self->outputxml() ); $PARSER->class_resolver( $self->get_class_resolver() ); # if we had no success (Transport layer error status code) - # or if transport layer failed - if (! $soap->transport->is_success() ) { + # or if transport layer failed + if ($response->code() != 200) { # Try deserializing response - there may be some - if ($response) { - eval { $PARSER->parse( $response ); }; - if ($@) { - warn "could not deserialize response: $@"; - } - else { - return $PARSER->get_data(); - } + if ($response->content) { + eval { $PARSER->parse( $response->content() ); }; + return $PARSER->get_data() if (not $@); + warn "could not deserialize response: $@"; }; - - require SOAP::WSDL::SOAP::Typelib::Fault11; # generate & return fault if we cannot serialize response # or have none... - return SOAP::WSDL::SOAP::Typelib::Fault11->new({ + return $fault_class_of{$ident}->new({ faultcode => 'soap:Server', faultactor => 'urn:localhost', faultstring => 'Error sending / receiving message: ' - . $soap->transport->message() + . $response->message() + #$soap->transport->message() }); } - eval { $PARSER->parse( $response ) }; + eval { $PARSER->parse( $response->content() ) }; # return fault if we cannot deserialize response if ($@) { - return SOAP::WSDL::SOAP::Typelib::Fault11->new({ + return $fault_class_of{$ident}->new({ faultcode => 'soap:Server', faultactor => 'urn:localhost', faultstring => "Error deserializing message: $@. \n" @@ -133,6 +153,52 @@ sub call { 1; =pod + +=head1 NAME + +SOAP::WSDL::Client - SOAP::WSDL's SOAP Client + +=head1 METHODS + +=head2 call + +=head2 Configuration methods + +=head3 outputxml + + $soap->outputxml(1); + +When set, call() returns the raw XML of the SOAP Envelope. + +=head3 set_content_type + + $soap->set_content_type('application/xml; charset: utf8'); + +Sets the content type and character encoding. + +You probably should not use a character encoding different from utf8: +SOAP::WSDL::Client will not convert the request into a different encoding +(yet). + +To leave out the encoding, just set the content type without appendet charset +like in + + text/xml + +Default: + + text/xml; charset: utf8 + +=head3 set_trace + + $soap->set_trace(1); + $soap->set_trace( sub { Log::Log4perl::get_logger()->debug( @_ ) } ); + +When set to a true value, tracing (via warn) is enabled. + +When set to a code reference, this function will be called on every +trace call, making it really easy for you to set up log4perl logging +or whatever you need. =head2 Features different from SOAP::Lite @@ -145,7 +211,7 @@ Nonetheless SOAP::WSDL mimics part of SOAP::Lite's API and behaviour, so SOAP::Lite users can switch without looking up every method call in the documentation. -A few things are quite differentl from SOAP::Lite, though: +A few things are quite different from SOAP::Lite, though: =head3 SOAP request data @@ -197,9 +263,28 @@ SOAP::WSDL::Client and implementing something like $soap_wsdl_client->call( mySoapMethod, @_); } -You may even do this in a class factory - SOAP::WSDL provides the methods -for generating such interfaces. +You may even do this in a class factory - see L for creating +such interfaces. + +=head3 Debugging / Tracing + +While SOAP::Lite features a global tracing facility, SOAP::WSDL::Client +allows to switch tracing on/of on a per-object base. + +See L on how to enable tracing. + +=head1 LICENSE + +Copyright 2004-2007 Martin Kutter. + +This file is part of SOAP-WSDL. You may distribute/modify it under +the same terms as perl itself + +=head1 AUTHOR + +Martin Kutter Emartin.kutter fen-net.deE =cut + diff --git a/lib/SOAP/WSDL/Definitions.pm b/lib/SOAP/WSDL/Definitions.pm index 7972ff8..dca34cf 100644 --- a/lib/SOAP/WSDL/Definitions.pm +++ b/lib/SOAP/WSDL/Definitions.pm @@ -1,4 +1,5 @@ package SOAP::WSDL::Definitions; +use utf8; use strict; use warnings; use Carp; @@ -49,8 +50,7 @@ sub explain { my $txt = ''; for my $service (@{ $self->get_service() }) { - $txt .= $service->explain( $opt ); - $txt .= "\n"; + $txt .= $service->explain( $opt ) . "\n"; } return $txt; } @@ -101,49 +101,62 @@ sub create { sub _create_interface { my $self = shift; my $opt = shift; - my $service_name = $opt->{ service }->get_name(); - my $file_name = "$opt->{ base_path }/$opt->{ interface_prefix }/$service_name.pm"; - $file_name =~s{::}{/}gms; - my $path = dirname $file_name; - my $name = basename $file_name; + my $service_name = $opt->{ service }->get_name(); + $service_name =~s{\.}{\:\:}xmsg; + # TODO: iterate over ports. + # - ignore non-SOAP ports + # - generate interface for all SOAP ports... my $binding = $self->find_binding( $self->_expand( $opt->{ service }->first_port()->get_binding() ) ); - my $portType = $self->find_portType( $self->_expand( $binding->get_type() ) ); - my $portType_operation_from_ref = $portType->get_operation(); + my $porttype = $self->find_portType( $self->_expand( $binding->get_type() ) ); + + my $port_operation_ref = $porttype->get_operation(); my $operation_ref = $binding->get_operation(); - $operation_ref = [ $operation_ref ] if ref $operation_ref ne 'ARRAY'; - my %operations = map { - do { - my $operation_name = $_->get_name(); - my $port_op = first { $_->get_name eq $operation_name } @{ $portType_operation_from_ref }; - my $input = $port_op->first_input()->get_message(); - my $message = $self->find_message( $self->_expand( $input ) ); - my $parts = $message->get_part; - ( $operation_name => [ - map { - do { - my $name; - ($name = $_->get_element()) - ? do { - my ($prefix, $localname) = split m{:}xms , $name; - "$opt->{ element_prefix }$localname"; - } - : ($name = $_->get_type()) - ? do { - my ($prefix, $localname) = split m{:}xms , $name; - "$opt->{ type_prefix }$localname"; - } - : () - } - } @{ $parts } - ] ); - }; - } @{ $operation_ref }; - -# use Data::Dumper; -# die Dumper \%operations; - + + # make up operations map - name => [ part types / elements class names ] + # + my %operations = (); + for my $operation ( @{ $operation_ref } ) { + my $operation_name = $operation->get_name(); + my $port_op = first { $_->get_name() eq $operation_name } @{ $port_operation_ref }; + + $operations{ $operation_name }->{ documentation } = $port_op->get_documentation(); + + my %msg_from = ( + 'input' => ($port_op->first_input() ) ? $port_op->first_input()->get_message() : undef, + 'output' => ($port_op->first_output()) ? $port_op->first_output()->get_message(): undef, + 'fault' => ($port_op->first_fault()) ? $port_op->first_fault()->get_message() : undef, + ); + + for my $msg (keys %msg_from) { + next if not $msg_from{ $msg }; + for my $part (@{ $self->find_message( $self->_expand( $msg_from{$msg} ) )->get_part }) { + my $name; + if (my $element_name = $part->get_element() ) { + $name = $element_name; + push @{ $operations{ $operation_name }->{$msg}->{ types } }, + $self->first_types()->find_element( $self->_expand( $element_name ) ) + ->explain({ wsdl => $self , anonymous => 1 }); + + } + elsif (my $type_name = $part->get_element() ) { + push @{ $operations{ $operation_name }->{$msg}->{ types } }, + $self->first_types()->find_type( $self->_expand( $element_name ) ) + ->explain({ wsdl => $self }); + $name = $type_name; + } + my ($prefix, $localname) = split m{:}xms , $name; + push @{ $operations{ $operation_name }->{$msg}->{ class } }, + "$opt->{ element_prefix }$localname"; + } + } + } + +# use Data::Dumper; +# die Dumper \%operations; + my $template = <<'EOT'; -package [% interface_prefix %][% service.get_name %]; +package [% interface_prefix %][% service.get_name.replace('\.', '::') %]; + use strict; use warnings; use [% typemap_prefix %][% service.get_name %]; @@ -153,7 +166,7 @@ sub new { my $class = shift; my $arg_ref = shift || {}; my $self = $class->SUPER::new({ - class_resolver => '[% typemap_prefix %][% service.get_name %]', + class_resolver => '[% typemap_prefix %][% service.get_name.replace('\.', '::') %]', proxy => '[% service.first_port.get_location %]', %{ $arg_ref } }); @@ -162,7 +175,7 @@ sub new { __PACKAGE__->__create_methods( [% FOREACH name = operations.keys -%] - [% name %] => [ [% FOREACH class = operations.$name %]'[% class %]', [% END %]], + [% name %] => [ [% FOREACH class = operations.$name.input.class %]'[% class %]', [% END %]], [% END %] ); @@ -183,17 +196,38 @@ __END__ my $[% operations.keys.1 %] = $interface->[% operations.keys.1 %](); -[% service.explain({ wsdl => wsdl }) %] +=head1 METHODS +[% FOREACH name=operations.keys; + operation=operations.$name; +%] +=head2 [% name %] + +[% operation.documentation %] + +SYNOPSIS: + + $service->[% name %]({ +[% FOREACH type = operation.input.types; type; END %] }); + +[% END %] =cut EOT require Template; + my $file_name = "$opt->{ base_path }/$opt->{ interface_prefix }/$service_name.pm"; + $file_name =~s{::}{/}gms; + my $path = dirname $file_name; + my $name = basename $file_name; my $tt = Template->new( OUTPUT_PATH => $path, ); - $tt->process(\$template, { %{ $opt }, operations => \%operations, binding => $binding, wsdl => $self }, $name) + $tt->process(\$template, + { %{ $opt }, operations => \%operations, binding => $binding, wsdl => $self }, + $name, + binmode => ':utf8' + ) or die $tt->error(); return 1; } @@ -203,7 +237,8 @@ EOT sub _create_typemap { my $self = shift; my $opt = shift; - my $service_name = $opt->{ service }->get_name(); + my $service_name = $opt->{ service }->get_name(); + $service_name =~s{\.}{\:\:}xmsg; my $file_name = "$opt->{ base_path }/$opt->{ typemap_prefix }/$service_name.pm"; $file_name =~s{::}{/}gms; my $path = dirname $file_name; @@ -212,11 +247,19 @@ sub _create_typemap { my $typemap = $opt->{ service }->to_typemap( { %{ $opt }, wsdl => $self } ); my $template = <<'EOT'; -package [% typemap_prefix %][% service.get_name %]; +package [% typemap_prefix %][% service.get_name.replace('\.', '::') %]; use strict; use warnings; -my %typemap = ( +my %typemap = ( +# SOAP 1.1 fault typemap +'Fault' => 'SOAP::WSDL::SOAP::Typelib::Fault11', +'Fault/faultcode' => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', +'Fault/faultactor' => 'SOAP::WSDL::XSD::Typelib::Builtin::TOKEN', +'Fault/faultstring' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', +'Fault/detail' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', + +# generated typemap [% typemap %] [% custom_types %] ); @@ -237,7 +280,11 @@ EOT my $tt = Template->new( OUTPUT_PATH => $path, ); - $tt->process(\$template, { %{ $opt }, typemap => $typemap }, $name) + $tt->process(\$template, + { %{ $opt }, typemap => $typemap }, + $name, + binmode => ':utf8', + ) or die $tt->error(); } diff --git a/lib/SOAP/WSDL/Expat/MessageParser.pm b/lib/SOAP/WSDL/Expat/MessageParser.pm index 8c5e6f2..19e44f8 100644 --- a/lib/SOAP/WSDL/Expat/MessageParser.pm +++ b/lib/SOAP/WSDL/Expat/MessageParser.pm @@ -5,23 +5,23 @@ use warnings; use SOAP::WSDL::XSD::Typelib::Builtin; use XML::Parser::Expat; -=pod - -=head2 new - -=over - -=item SYNOPSIS - - my $obj = ->new(); - -=item DESCRIPTION - -Constructor. - -=back - -=cut +=pod + +=head2 new + +=over + +=item SYNOPSIS + + my $obj = ->new(); + +=item DESCRIPTION + +Constructor. + +=back + +=cut sub new { my $class = shift; @@ -37,106 +37,131 @@ sub class_resolver { my $self = shift; $self->{ class_resolver } = shift; } - -sub parse { - my $self = shift; - my $xml = shift; + +sub _initialize { + my $self = shift; + $self->{ data } = undef; my $characters; - my $current = '__STOP__'; - my $ignore = [ 'Envelope', 'Body' ]; - my $list = []; - my $namespace = {}; - my $path = []; + my $current = undef; + my $ignore = [ 'Envelope', 'Body' ]; # top level elements to ignore + my $list = []; # node list + my $path = []; # current path (without + # number) + my $skip = 0; # skip elements my $parser = XML::Parser::Expat->new(); + # use "globals" for speed + my ($_prefix, $_localname, $_element, $_method, + $_class, $_parser, %_attrs) = (); + no strict qw(refs); $parser->setHandlers( Start => sub { - my ($parser, $element, %attrs) = @_; - my ($prefix, $localname) = split m{:}xms , $element; - # for non-prefixed elements - if (not $localname) { - $localname = $element; - $prefix = q{}; - } + ($_parser, $_element, %_attrs) = @_; + ($_prefix, $_localname) = split m{:}xms , $_element; + + $_localname ||= $_element; # for non-prefixed elements + # ignore top level elements - if (@{ $ignore } && $localname eq $ignore->[0]) { + if (@{ $ignore } && $_localname eq $ignore->[0]) { shift @{ $ignore }; return; } - # empty characters - $characters = q{}; - - push @{ $path }, $localname; # step down... - push @{ $list }, $current; # remember current - + + push @{ $path }, $_localname; # step down in path + return if $skip; # skip inside __SKIP__ + # resolve class of this element - my $class = $self->{ class_resolver }->get_class( $path ) + $_class = $self->{ class_resolver }->get_class( $path ) or die "Cannot resolve class for " . join('/', @{ $path }) . " via $self->{ class_resolver }"; - + + if ($_class eq '__SKIP__') { + $skip = join '/', @{ $path }; + return; + } + + push @$list, $current; # step down in tree ()remember current) + + $characters = q{}; # empty characters + # Check whether we have a primitive - we implement them as classes # TODO replace with UNIVERSAL->isa() # match is a bit faster if the string does not match, but WAY slower # if $class matches... - # if (not $class=~m{^SOAP::WSDL::XSD::Typelib::Builtin}xms) { - - if (index $class, 'SOAP::WSDL::XSD::Typelib::Builtin', 0 < 0) { - + + if (index $_class, 'SOAP::WSDL::XSD::Typelib::Builtin', 0 < 0) { # check wheter there is a CODE reference for $class::new. # If not, require it - all classes required here MUST # define new() # This is the same as $class->can('new'), but it's way faster - *{ "$class\::new" }{ CODE } - or eval "require $class" ## no critic qw(ProhibitStringyEval) + *{ "$_class\::new" }{ CODE } + or eval "require $_class" ## no critic qw(ProhibitStringyEval) or die $@; } - # create object - # set current object - $current = $class->new({ %attrs }); + + $current = $_class->new({ %_attrs }); # set new current object # remember top level element defined $self->{ data } or ($self->{ data } = $current); }, + Char => sub { + return if $skip; $characters .= $_[1]; }, + End => sub { - my $element = $_[1]; + $_element = $_[1]; - my ($prefix, $localname) = split m{:}xms , $element; - # for non-prefixed elements - if (not $localname) { - $localname = $element; - $prefix = q{}; + ($_prefix, $_localname) = split m{:}xms , $_element; + $_localname ||= $_element; # for non-prefixed elements + + pop @{ $path }; # step up in path + + if ($skip) { + return if $skip ne join '/', @{ $path }, $_localname; + $skip = 0; + return; } # This one easily handles ignores for us, too... - return if not ref $list->[-1]; + return if not ref $$list[-1]; - if ( $current - ->isa('SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType') ) { + # set characters in current if we are a simple type + # we may have characters in complexTypes with simpleContent, + # too - maybe we should rely on the presence of characters ? + # may get a speedup by defining a ident method in anySimpleType + # and looking it up via exists &$class::ident; + if ( $current->isa('SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType') ) { $current->set_value( $characters ); } + # currently doesn't work, as anyType does not implement value - + # maybe change ? +# $current->set_value( $characters ) if ($characters); # set appropriate attribute in last element # multiple values must be implemented in base class - my $method = "add_$localname"; - - $list->[-1]->$method( $current ); - - # step up in path - pop @{ $path }; - - # step up in object hierarchy... - $current = pop @{ $list }; + $_method = "add_$_localname"; + $$list[-1]->$_method( $current ); + + $current = pop @$list; # step up in object hierarchy... } ); - - $parser->parse( $xml ); + return $parser; +} + +sub parse { + $_[0]->_initialize->parse( $_[1] ); + return $_[0]->{ data }; +} + +sub parsefile { + $_[0]->_initialize->parsefile( $_[1] ); + return $_[0]->{ data }; } sub get_data { @@ -144,7 +169,6 @@ sub get_data { return $self->{ data }; } - 1; =pod @@ -167,6 +191,13 @@ Real fast expat based SOAP message parser. See L for details. +=head2 Skipping unwanted items + +Sometimes there's unneccessary information transported in SOAP messages. + +To skip XML nodes (including all child nodes), just edit the type map for +the message and set the type map entry to '__SKIP__'. + =head1 Bugs and Limitations =over diff --git a/lib/SOAP/WSDL/Manual.pod b/lib/SOAP/WSDL/Manual.pod index 0d8caad..c70079c 100644 --- a/lib/SOAP/WSDL/Manual.pod +++ b/lib/SOAP/WSDL/Manual.pod @@ -4,7 +4,7 @@ SOAP::WSDL::Manual - accessing WSDL based web services -=head1 Intro: Accessing a WSDL-based web service +=head1 Accessing a WSDL-based web service =head2 Quick walk-through for the unpatient @@ -57,6 +57,152 @@ classes and returned to the user as objects. To find out which class a particular XML node should be, SOAP::WSDL uses typemaps. For every Web service, there's also a typemap created. +=head2 Interface class creation + +To create interface classes, follow the steps above from +L. + +If this works fine for you, skip the next paragraphs. If not, read on. + +The steps to instrument a web service with SOAP::WSDL perl bindings +(in detail) are as follows: + +=over + +=item * Gather web service information + +You'll need to know at least a URL pointing to the web service's WSDL +definition. + +If you already know more - like which methods the service provides, or +how the XML messages look like, that's fine. All these things will help you +later. + +=item * Create WSDL bindings + + perl wsdl2perl.pl -b base_dir URL + +This will generate the perl bindings in the directory specified by base_dir. + +For more options, see L - you may want to specify class +prefixes for XML type and element classes, type maps and interface classes, +and you may even want to add custom typemap elements. + +=item * Check the result + +There should be a bunch of classes for types (in the MyTypes:: namespace by +default), elements (in MyElements::), and at least one typemap +(in MyTypemaps::) and one interface class (in MyInterfaces::). + +If you don't already know the details of the web service you're going to +instrument, it's now time to read the perldoc of the generated interface +classes. It will tell you what methods each service provides, and which +parameters they take. + +If the WSDL definition is informative about what these methods do, +the included perldoc will be, too. + +=item * Write a perl script (or module) accessing the web service. + + use MyInterface::SERVICE_NAME; + my $service = MyInterface::SERVICE_NAME->new(); + + my $result = $service->SERVICE_METHOD(); + die $result if not $result; + print $result; + +The above handling of errors ("die $result if not $result") may look a bit +strange - it is due to the nature of +L +objects SOAP::WSDL uses for signalling failure. + +These objects are false in boolean context, but serialize to their XML structure +on stringification. + +You may, of course, access individual fault properties, too. To get a list of +fault properties, see L + +=back + +=head2 Adding missing information + +Sometimes, WSDL definitions are incomplete. In most of these cases, proper +fault definitions are missing. This means that though the specification sais +nothing about it, Fault messages include extra elements in the +EdetailE section, or faults are even indicated by non-fault messages. + +There are two steps you need to perform for adding additional information. + +=over + +=item * Provide required type classes + +For each extra data type used in the XML messages, a type class has to be +created. + +It is strongly discouraged to use the same namespace for hand-written and +generated classes - while generated classes may be many, you probably will +only implement a few by hand. These (precious) few classes may get lost +in the mass of (cheap) generated ones. Just imagine one of your co-workers +(or even yourself) deleting the whole bunch and re-generating everything +- oops - almost everything. You got the point. + +For simplicity, you probably just want to use builtin types wherever +possible - you are probably not interested in whether a fault detail's +error code is presented to you as a simpleType ranging from 1 to 10 +(which you have to write) or as a int (which is a builtin type ready +to use). + +Using builtin types for simpleType definitions may greatly reduce the +number of additional classes you need to implement. + +If the extra type classes you need include EcomplexType E +or Eelement /E definitions, see +L and +L on how to create ComplexType +and Element type classes. + +=item * Provide a typemap snippet to wsdl2perl.pl + +SOAP::WSDL uses typemaps for finding out into which class' object +a XML node should be transformed. + +Typemaps basically map the path of every XML element inside the Body +tag to a perl class. + +Typemap snippets have to look like this (which is actually the +default Fault typemap included in every generated one): + + 'Fault' => 'SOAP::WSDL::SOAP::Typelib::Fault11', + 'Fault/faultcode' => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', + 'Fault/faultactor' => 'SOAP::WSDL::XSD::Typelib::Builtin::anyURI', + 'Fault/faultstring' => 'SOAP::WSDL::XSD::Typelib::Builtin::string', + 'Fault/detail' => 'SOAP::WSDL::XSD::Typelib::Builtin::anyType', + +The lines are hash key - value pairs. The keys are the XPath +expression relative to the Body element. + +If you don't know about XPath: They are just the names of the XML +tags, starting from the one inside EBodyE up to the current +one joined by /. + +One line for every XML node is required. + +You may use all builtin, generated or custom type class names as +values. + +Use wsdl2perl.pl -mi=FILE to include custom typemap snippets. + +Your extra statements are included last, so they override potential +typemap statements with the same keys. + +=back + +=head1 Accessing a web service without a WSDL definition + +=head1 Troubleshooting + + =head1 SEE ALSO L The meaning of all these words diff --git a/lib/SOAP/WSDL/Manual/Glossary.pod b/lib/SOAP/WSDL/Manual/Glossary.pod index c0e4bc1..8cdabb9 100644 --- a/lib/SOAP/WSDL/Manual/Glossary.pod +++ b/lib/SOAP/WSDL/Manual/Glossary.pod @@ -7,7 +7,7 @@ SOAP::WSDL::Manual::Glossary - Those acronyms and stuff =head2 web service Web services are RPC (Remote Procedure Call) interfaces accessible via -the internet, typically via HTTP(S). +some internet protocol, typically via HTTP(S). =head2 SOAP @@ -21,7 +21,7 @@ SOAP services accessible via postcards. Despite it's name, SOAP has nothing more to do with objects than cars have with pets - SOAP messages may, but not neccessarily do -carry object, very much like your car may, but does not need to +carry objects, very much like your car may, but does not need to carry your pet. =head2 WSDL diff --git a/lib/SOAP/WSDL/Operation.pm b/lib/SOAP/WSDL/Operation.pm index 8050c8f..203611c 100644 --- a/lib/SOAP/WSDL/Operation.pm +++ b/lib/SOAP/WSDL/Operation.pm @@ -3,7 +3,10 @@ use strict; use warnings; use Class::Std::Storable; use base qw/SOAP::WSDL::Base/; - + +# this class may be used for both soap::operation and wsdl::operation. +# which one it is depends on context... + my %operation_of :ATTR(:name :default<()>); my %input_of :ATTR(:name :default<()>); my %output_of :ATTR(:name :default<()>); diff --git a/lib/SOAP/WSDL/Parser.pod b/lib/SOAP/WSDL/Parser.pod index 7fb9077..ec53512 100644 --- a/lib/SOAP/WSDL/Parser.pod +++ b/lib/SOAP/WSDL/Parser.pod @@ -90,6 +90,29 @@ A class resolver package might look like this: }; 1; +=head3 Skipping unwanted items + +Sometimes there's unneccessary information transported in SOAP messages. + +To skip XML nodes (including all child nodes), just edit the type map for +the message and set the type map entry to '__SKIP__'. + +In the example above, EnqueueMessage/StuffIDontNeed and all child elements +are skipped. + + my %class_list = ( + 'EnqueueMessage' => 'Typelib::TEnqueueMessage', + 'EnqueueMessage/MMessage' => 'Typelib::TMessage', + 'EnqueueMessage/MMessage/MRecipientURI' => 'SOAP::WSDL::XSD::Builtin::anyURI', + 'EnqueueMessage/MMessage/MMessageContent' => 'SOAP::WSDL::XSD::Builtin::string', + 'EnqueueMessage/StuffIDontNeed' => '__SKIP__', + 'EnqueueMessage/StuffIDontNeed/Foo' => 'SOAP::WSDL::XSD::Builtin::string', + 'EnqueueMessage/StuffIDontNeed/Bar' => 'SOAP::WSDL::XSD::Builtin::string', + ); + +Note that only SOAP::WSDL::Expat::MessageParser implements skipping elements +at the time of writing. + =head3 Creating type lib classes Every element must have a correspondent one in the type library. diff --git a/lib/SOAP/WSDL/SAX/WSDLHandler.pm b/lib/SOAP/WSDL/SAX/WSDLHandler.pm index 6b1da16..96b08cd 100644 --- a/lib/SOAP/WSDL/SAX/WSDLHandler.pm +++ b/lib/SOAP/WSDL/SAX/WSDLHandler.pm @@ -3,13 +3,13 @@ use strict; use warnings; use Carp; use Class::Std::Storable; -# use base qw(XML::SAX::Base); use SOAP::WSDL::TypeLookup; my %tree_of :ATTR(:name :default<{}>); my %order_of :ATTR(:name :default<[]>); my %targetNamespace_of :ATTR(:name :default<()>); my %current_of :ATTR(:name :default<()>); +my %characters_of :ATTR(); { # we have to implement our own new - we need a blessed Hash ref as $self @@ -34,7 +34,6 @@ my %current_of :ATTR(:name :default<()>); # ...we ignore em all... no strict qw(refs); foreach my $method ( qw( - characters processing_instruction ignorable_whitespace set_document_locator @@ -87,7 +86,8 @@ sub start_element { $element->{ NamespaceURI }, $element->{ LocalName } ); - + + $characters_of{ $ident } = q{}; return if not $action; if ($action->{ type } eq 'CLASS') { @@ -138,28 +138,41 @@ sub start_element { : values %{ $element->{ Attributes } } ); } } + +sub characters { + $characters_of{ ident $_[0] } .= $_[1]->{ Data }; +} sub end_element { - my ($self, $element) = @_; + my ($self, $element) = @_; my $ident = ident $self; - my $action = SOAP::WSDL::TypeLookup->lookup( - $element->{ NamespaceURI }, - $element->{ LocalName } - ) || {}; + my $action = SOAP::WSDL::TypeLookup->lookup( + $element->{ NamespaceURI }, + $element->{ LocalName } + ) || {}; - if ($action->{ type } && $action->{ type } eq 'CLASS' ) - { - $current_of{ $ident } = pop @{ $order_of{ $ident } }; - } + return if not ($action->{ type }); + if ( $action->{ type } eq 'CLASS' ) { + $current_of{ $ident } = pop @{ $order_of{ $ident } }; + } + elsif ($action->{ type } eq 'CONTENT' ) { + my $method = $action->{ method }; + no strict qw(refs); + # strip of leading and trailing whitespace + $characters_of{ $ident } =~s{ ^ \s+ (.+) \s+ $ }{$1}xms; + # replace multi whitespace by one + $characters_of{ $ident } =~s{ \s+ }{ }xmsg; + $current_of{ $ident }->$method( $characters_of{ $ident } ); + } } sub fatal_error { - die @_; + die @_; } sub get_data { - my $self = shift; - return $tree_of{ ident $self }; + my $self = shift; + return $tree_of{ ident $self }; } 1; diff --git a/lib/SOAP/WSDL/TypeLookup.pm b/lib/SOAP/WSDL/TypeLookup.pm index d1cc4c8..6418ffc 100644 --- a/lib/SOAP/WSDL/TypeLookup.pm +++ b/lib/SOAP/WSDL/TypeLookup.pm @@ -1,129 +1,157 @@ package SOAP::WSDL::TypeLookup; my %TYPES = ( - # wsdl: - 'http://schemas.xmlsoap.org/wsdl/' => { - binding => { - type => 'CLASS', - class => 'SOAP::WSDL::Binding', - }, - definitions => { - type => 'CLASS', - class => 'SOAP::WSDL::Definitions', - }, - portType => { - type => 'CLASS', - class => 'SOAP::WSDL::PortType', - }, - types => { - type => 'SKIP', - }, - message => { - type => 'CLASS', - class => 'SOAP::WSDL::Message', - }, - part => { - type => 'CLASS', - class => 'SOAP::WSDL::Part', - }, - service => { - type => 'CLASS', - class => 'SOAP::WSDL::Service', - }, - port => { - type => 'CLASS', - class => 'SOAP::WSDL::Port', - }, - operation => { - type => 'CLASS', - class => 'SOAP::WSDL::Operation', - }, - input => { - type => 'CLASS', - class => 'SOAP::WSDL::OpMessage', - }, - output => { - type => 'CLASS', - class => 'SOAP::WSDL::OpMessage', - }, - fault => { - type => 'CLASS', - class => 'SOAP::WSDL::OpMessage', - }, - types => { - type => 'CLASS', - class => 'SOAP::WSDL::Types', - } - }, - # soap: - 'http://schemas.xmlsoap.org/wsdl/soap/' => { - operation => { - type => 'CLASS', - class => 'SOAP::WSDL::SoapOperation', - }, - binding => { - type => 'PARENT', - }, - body => { - type => 'PARENT', - }, - address => { - type => 'PARENT', - } - }, - 'http://www.w3c.org/2001/XMLSchema' => { - schema => { - type => 'CLASS', - class => 'SOAP::WSDL::XSD::Schema', - }, - element => { - type => 'CLASS', - class => 'SOAP::WSDL::XSD::Element', - }, - simpleType => { - type => 'CLASS', - class => 'SOAP::WSDL::XSD::SimpleType', - }, - complexType => { - type => 'CLASS', - class => 'SOAP::WSDL::XSD::ComplexType', - }, + # wsdl: + 'http://schemas.xmlsoap.org/wsdl/' => { + binding => { + type => 'CLASS', + class => 'SOAP::WSDL::Binding', + }, + definitions => { + type => 'CLASS', + class => 'SOAP::WSDL::Definitions', + }, + portType => { + type => 'CLASS', + class => 'SOAP::WSDL::PortType', + }, + message => { + type => 'CLASS', + class => 'SOAP::WSDL::Message', + }, + part => { + type => 'CLASS', + class => 'SOAP::WSDL::Part', + }, + service => { + type => 'CLASS', + class => 'SOAP::WSDL::Service', + }, + port => { + type => 'CLASS', + class => 'SOAP::WSDL::Port', + }, + operation => { + type => 'CLASS', + class => 'SOAP::WSDL::Operation', + }, + input => { + type => 'CLASS', + class => 'SOAP::WSDL::OpMessage', + }, + output => { + type => 'CLASS', + class => 'SOAP::WSDL::OpMessage', + }, + fault => { + type => 'CLASS', + class => 'SOAP::WSDL::OpMessage', + }, + types => { + type => 'CLASS', + class => 'SOAP::WSDL::Types', + }, + documentation => { + type => 'CONTENT', + method => 'set_documentation', + } + }, + # soap: + 'http://schemas.xmlsoap.org/wsdl/soap/' => { + operation => { + type => 'CLASS', + class => 'SOAP::WSDL::SoapOperation', + }, + binding => { + type => 'PARENT', + }, + body => { + type => 'PARENT', + }, + address => { + type => 'PARENT', + } + }, + 'http://www.w3.org/2001/XMLSchema' => { + schema => { + type => 'CLASS', + class => 'SOAP::WSDL::XSD::Schema', + }, + attribute => { + type => 'SKIP' # not implemented yet + }, + attributeGroup => { + type => 'SKIP', # not implemented yet + }, + key => { + type => 'SKIP', # not implemented yet + }, + keyref => { + type => 'SKIP', # not implemented yet + }, + unique => { + type => 'SKIP', # not implemented yet + }, + notation => { + type => 'SKIP', # not implemented yet + }, + annotation => { + type => 'SKIP', # not implemented yet + }, + appinfo => { + type => 'SKIP', # not implemented yet + }, + description => { + type => 'SKIP', # not implemented yet + }, + element => { + type => 'CLASS', + class => 'SOAP::WSDL::XSD::Element', + }, + simpleType => { + type => 'CLASS', + class => 'SOAP::WSDL::XSD::SimpleType', + }, + complexType => { + type => 'CLASS', + class => 'SOAP::WSDL::XSD::ComplexType', + }, simpleContent => { type => 'METHOD', method => 'set_contentModel', value => 'simpleContent' + }, + complexContent => { + type => 'METHOD', + method => 'set_contentModel', + value => 'complexContent' + }, + restriction => { + type => 'METHOD', + method => 'set_restriction', }, - complexContent => { + list => { type => 'METHOD', - method => 'set_contentModel', - value => 'complexContent' + method => 'set_list', + }, + union => { + type => 'METHOD', + method => 'set_union', + }, + enumeration => { + type => 'METHOD', + method => 'push_enumeration', }, - restriction => { - type => 'METHOD', - method => 'set_restriction', - }, - list => { - type => 'METHOD', - method => 'set_list', - }, - union => { - type => 'METHOD', - method => 'set_union', - }, - enumeration => { - type => 'METHOD', - method => 'push_enumeration', - }, group => { type => 'METHOD', method => 'set_flavor', value => 'all', }, - all => { - type => 'METHOD', - method => 'set_flavor', - value => 'all', - }, + all => { + type => 'METHOD', + method => 'set_flavor', + value => 'all', + }, choice => { type => 'METHOD', method => 'set_flavor', @@ -133,16 +161,10 @@ my %TYPES = ( type => 'METHOD', method => 'set_flavor', value => 'sequence', - }, - - }, + }, + }, ); -# thei're equal (typo ?) -$TYPES{ 'http://www.w3.org/2001/XMLSchema' } = $TYPES{ - 'http://www.w3c.org/2001/XMLSchema' -}; - sub lookup { my $self = shift; diff --git a/lib/SOAP/WSDL/XSD/Builtin.pm b/lib/SOAP/WSDL/XSD/Builtin.pm new file mode 100644 index 0000000..b787c91 --- /dev/null +++ b/lib/SOAP/WSDL/XSD/Builtin.pm @@ -0,0 +1,46 @@ +package SOAP::WSDL::XSD::Builtin; +use strict; +use warnings; +use Class::Std::Storable; +use base qw(SOAP::WSDL::Base); +use Data::Dumper; + +sub serialize { + my ($self, $name, $value, $opt) = @_; + my $xml; + $opt->{ indent } ||= ""; + $opt->{ attributes } ||= []; + + $xml .= $opt->{ indent } if ($opt->{ readable }); + $xml .= '<' . join ' ', $name, @{ $opt->{ attributes } }; + if ( $opt->{ autotype }) { + my $ns = $self->get_targetNamespace(); + my $prefix = $opt->{ namespace }->{ $ns } + || die 'No prefix found for namespace '. $ns; + $xml .= ' type="' . $prefix . ':' + . $self->get_name() . '"' if ($self->get_name() ); + } + + if (defined $value) { + $xml .= '>'; + $xml .= "$value"; + $xml .= '' ; + } + else { + $xml .= '/>'; + } + $xml .= "\n" if ($opt->{ readable }); + return $xml; +} + +sub explain { + my ($self, $opt, $name ) = @_; + $opt->{ indent } = q{} if not defined $opt->{ indent }; + return "$opt->{ indent }'$name' => \$someValue,\n" +} + +sub toClass { + warn "# builtin"; +} + +1; diff --git a/lib/SOAP/WSDL/XSD/ComplexType.pm b/lib/SOAP/WSDL/XSD/ComplexType.pm index e8f3bd3..8c3a523 100644 --- a/lib/SOAP/WSDL/XSD/ComplexType.pm +++ b/lib/SOAP/WSDL/XSD/ComplexType.pm @@ -127,16 +127,15 @@ sub explain { return q{} if not $flavor; # empty complexType $opt->{ indent } ||= q{ }; - my $xml = q{}; - $xml .= "$opt->{indent}\'$name'=> " if $name; + my $xml = q{}; + $xml .= "$opt->{indent}\'$name'=> {\n" if $name; if ( ($flavor eq "sequence") or ($flavor eq "all") ) { - $xml .= "{\n"; - $opt->{ indent } .= " "; - $xml .= join q{}, map { $_->explain( $opt ) } - @{ $self->get_element() }; + $opt->{ indent } .= " "; + for my $element (@{ $self->get_element() }) { + $xml .= $element->explain( $opt ) + } $opt->{ indent } =~s/\s{2}$//; # step back - $xml .= "$opt->{ indent }},\n"; } elsif ($flavor eq "complexContent") { @@ -150,6 +149,11 @@ sub explain { { warn "found unsupported complexType definition $flavor"; } + + + # if we're called inside a element... + $xml .= "$opt->{indent}},\n" if $name; + return $xml; } @@ -300,26 +304,3 @@ sub _check_value { } 1; -=pod - -=head1 Bugs and limitations - -=over - -=item * attribute definitions - -Attribute definitions are ignored - -=item * abstract, block, final, mixed - -Handling of these attribute is not implemented yet. - -=item * simpleContent, complexContent, extension, restriction, group, choice - -Handling of these child elements is not implemented yet - -=item * explain may produce erroneous results - -=back - -=cut diff --git a/lib/SOAP/WSDL/XSD/Element.pm b/lib/SOAP/WSDL/XSD/Element.pm index 03aae59..be7f03b 100644 --- a/lib/SOAP/WSDL/XSD/Element.pm +++ b/lib/SOAP/WSDL/XSD/Element.pm @@ -111,18 +111,24 @@ sub serialize { sub explain { my ($self, $opt, $name) = @_; my $type; - my $text = q{}; + my $text = q{}; # if we have a simple / complexType, explain right here if ($type = $self->first_simpleType() ) { - $text .= $type->explain( $opt, $self->get_name() ); - return $text; + if ($opt->{ anonymous }) { + delete $opt->{ anonymous }; + return $type->explain( $opt, undef ); + } + return $type->explain( $opt, $self->get_name() ); } elsif ($type = $self->first_complexType() ) { - $text .= $type->explain( $opt, $self->get_name() ); - return $text; + if ($opt->{ anonymous }) { + delete $opt->{ anonymous }; + return $type->explain( $opt, undef ); + } + return $type->explain( $opt, $self->get_name() ); } # return if it's not a derived type - we don't handle @@ -137,12 +143,15 @@ sub explain { $ns_map{ $prefix }, $localname ); - use Data::Dumper; + use Data::Dumper; die "no type for $prefix:$localname ($ns_map{ $prefix })" . Dumper $opt->{ wsdl }->first_types()->first_schema()->_DUMP - - if (not $type); - + if (not $type); + + if ($opt->{ anonymous }) { + delete $opt->{ anonymous }; + return $text .= $type->explain( $opt, undef ); + } return $text .= $type->explain( $opt, $self->get_name() ); return 'ERROR: '. $@; } @@ -369,47 +378,4 @@ EOT 1; -__END__ -=pod - -=head1 NAME - -SOAP::WSDL::XSD::Element - XSD Element representation - -=head1 DESCRIPTION - -Represents a XML Schema Definition's Element element for serializing into -various forms. - -=head1 Bugs and limitations - -=over - -=item * block - -Handling of the block attribute is not implemented yet. - -=item * final - -Handling of the final attribute is not implemented yet. - -=item * substitutionGroup - -Handling of the substitutionGroup attribute is not implemented yet - -=item * explain may produce erroneous results - -=back - -=head1 COPYING - -This module is part of SOAP-WSDL. See SOAP::WSDL for license. - -=head1 AUTHOR - -Insert @ instead of whitespace into e-mail. - - Martin Kutter Emartin.kutter fen-net.deE - -=cut diff --git a/lib/SOAP/WSDL/XSD/Primitive.pm b/lib/SOAP/WSDL/XSD/Primitive.pm deleted file mode 100644 index 441204f..0000000 --- a/lib/SOAP/WSDL/XSD/Primitive.pm +++ /dev/null @@ -1,50 +0,0 @@ -package SOAP::WSDL::XSD::Primitive; -use strict; -use warnings; -use Class::Std::Storable; -use base qw(SOAP::WSDL::Base); -use Data::Dumper; - -sub serialize -{ - my ($self, $name, $value, $opt) = @_; - my $xml; - $opt->{ indent } ||= ""; - $opt->{ attributes } ||= []; - - $xml .= $opt->{ indent } if ($opt->{ readable }); - $xml .= '<' . join ' ', $name, @{ $opt->{ attributes } }; - if ( $opt->{ autotype }) - { - my $ns = $self->get_targetNamespace(); - my $prefix = $opt->{ namespace }->{ $ns } - || die 'No prefix found for namespace '. $ns; - $xml .= ' type="' . $prefix . ':' - . $self->get_name() . '"' if ($self->get_name() ); - } - - if (defined $value) - { - $xml .= '>'; - $xml .= "$value"; - $xml .= '' ; - } - else - { - $xml .= '/>'; - } - $xml .= "\n" if ($opt->{ readable }); - return $xml; -} - -sub explain -{ - my ($self, $opt, $name ) = @_; - $opt->{ indent } ||= ""; - return "$opt->{ indent }'$name' => \$someValue,\n" -} - -sub toClass { - warn "# primitive"; -} -1; diff --git a/lib/SOAP/WSDL/XSD/Schema/Builtin.pm b/lib/SOAP/WSDL/XSD/Schema/Builtin.pm index a607343..f132993 100644 --- a/lib/SOAP/WSDL/XSD/Schema/Builtin.pm +++ b/lib/SOAP/WSDL/XSD/Schema/Builtin.pm @@ -3,11 +3,11 @@ use strict; use warnings; use Class::Std::Storable; use SOAP::WSDL::XSD::Schema; -use SOAP::WSDL::XSD::Primitive; +use SOAP::WSDL::XSD::Builtin; use base qw(SOAP::WSDL::XSD::Schema); # all builtin types - add validation (e.g. content restrictions) later... -my %PRIMITIVES = ( +my %BUILTINS = ( 'string' => {}, 'boolean' => {}, 'decimal' => {}, @@ -50,9 +50,9 @@ sub START { my $self = shift; my @args = @_; - while (my ($name, $value) = each %PRIMITIVES ) + while (my ($name, $value) = each %BUILTINS ) { - $self->push_type( SOAP::WSDL::XSD::Primitive->new({ + $self->push_type( SOAP::WSDL::XSD::Builtin->new({ name => $name, targetNamespace => 'http://www.w3.org/2001/XMLSchema', } ) diff --git a/lib/SOAP/WSDL/XSD/SimpleType.pm b/lib/SOAP/WSDL/XSD/SimpleType.pm index f0bdfcf..ffdfd7e 100644 --- a/lib/SOAP/WSDL/XSD/SimpleType.pm +++ b/lib/SOAP/WSDL/XSD/SimpleType.pm @@ -43,8 +43,7 @@ sub set_union { } } -sub push_enumeration -{ +sub push_enumeration { my $self = shift; my @attr = @_; my @attributes = @_; @@ -101,13 +100,9 @@ sub _serialize_single { } sub explain { - my ($self, $opt, $name) = @_; - my $perl; - $opt->{ indent } ||= ""; - $perl .= $opt->{ indent } if ($opt->{ readable }); - $perl .= q{'} . $name . q{' => $someValue }; - $perl .= "\n" if ($opt->{ readable }); - return $perl; + my ($self, $opt, $name ) = @_; + $opt->{ indent } = q{} if not defined $opt->{ indent }; + return "$opt->{ indent }'$name' => \$someValue,\n" } sub _check_value { @@ -155,25 +150,4 @@ EOT return $code; } - 1; - -=pod - -=head1 Bugs and limitations - -=over - -=item * simpleContent, complexContent - -These child elements are not implemented yet - -=item * union - -union simpleType definitions probalbly serialize wrong - -=item * explain may produce erroneous results - -=back - -=cut diff --git a/lib/SOAP/WSDL/XSD/Typelib/Builtin.pm b/lib/SOAP/WSDL/XSD/Typelib/Builtin.pm index 3b3135c..b838991 100644 --- a/lib/SOAP/WSDL/XSD/Typelib/Builtin.pm +++ b/lib/SOAP/WSDL/XSD/Typelib/Builtin.pm @@ -205,7 +205,26 @@ byte integer objects. =head2 SOAP::WSDL::XSD::Typelib::Builtin::date +date values are automatically converted into XML date strings during setting: + + YYYY-MM-DD+zz:zz + +The time zone is set to the local time zone if not included. + +All input variants supported by Date::Parse are supported. You may even pass +in dateTime strings - the time part will be ignored. Note that +set_value is around 100 times slower when setting non-XML-time strings + =head2 SOAP::WSDL::XSD::Typelib::Builtin::dateTime + +dateTime values are automatically converted into XML dateTime strings during setting: + + YYYY-MM-DDThh:mm:ss.nnnnnnn+zz:zz + +The optional nanoseconds parts is excluded in converted values, as it would always be 0. + +All input variants supported by Date::Parse are supported. Note that +set_value is around 100 times slower when setting non-XML-time strings =head2 SOAP::WSDL::XSD::Typelib::Builtin::decimal @@ -237,6 +256,8 @@ decimal is the base of all non-float numbers =head2 SOAP::WSDL::XSD::Typelib::Builtin::IDREFS +List of SOAP::WSDL::XSD::Typelib::Builtin::IDREF objects. + Derived by SOAP::WSDL::XSD::Typelib::Builtin::list. =head2 SOAP::WSDL::XSD::Typelib::Builtin::int @@ -273,6 +294,18 @@ Derived by SOAP::WSDL::XSD::Typelib::Builtin::list. =head2 SOAP::WSDL::XSD::Typelib::Builtin::time +time values are automatically converted into XML time strings during setting: + + hh:mm:ss.nnnnnnn+zz:zz + hh:mm:ss+zz:zz + +The time zone is set to the local time zone if not included. The optional +nanoseconds part is not included in converted values, as it would always be 0. + +All input variants supported by Date::Parse are supported. You may even pass +in dateTime strings - the date part will be ignored. Note that +set_value is around 100 times slower when setting non-XML-time strings + =head2 SOAP::WSDL::XSD::Typelib::Builtin::token =head2 SOAP::WSDL::XSD::Typelib::Builtin::unsignedByte diff --git a/lib/SOAP/WSDL/XSD/Typelib/Builtin/date.pm b/lib/SOAP/WSDL/XSD/Typelib/Builtin/date.pm index cef704b..1a2bb30 100644 --- a/lib/SOAP/WSDL/XSD/Typelib/Builtin/date.pm +++ b/lib/SOAP/WSDL/XSD/Typelib/Builtin/date.pm @@ -2,6 +2,9 @@ package SOAP::WSDL::XSD::Typelib::Builtin::date; use strict; use warnings; +use Date::Parse; +use Date::Format; + my %pattern_of :ATTR(:name :default<()>); my %enumeration_of :ATTR(:name :default<()>); my %whiteSpace_of :ATTR(:name :default<()>); @@ -31,5 +34,32 @@ BEGIN { } +sub set_value { + # use set_value from base class if we have a XML-DateTime format + #2037-12-31T00:00:00.0000000+01:00 + if ( + $_[1] =~ m{ ^\d{4} \- \d{2} \- \d{2} + (:? [\+\-] \d{2} \: \d{2} )? + }xms + ) { + $_[0]->SUPER::set_value($_[1]) + } + # use a combination of strptime and strftime for converting the date + # Unfortunately, strftime outputs the time zone as [+-]0000, whereas XML + # whants it as [+-]00:00 + # We know that the latter part of the TZ is always :00 (there's no time + # zone whith minute offset yet), so we just use substr to get the part + # up to the last 2 timezone digits and append :00 + # We leave out the optional nanoseconds part, as it would always be empty. + else { + # strptime sets empty values to undef - and strftime doesn't like that... + my @time_from = map { ! defined $_ ? 0 : $_ } strptime($_[1]); + my $time_str = strftime( '%Y-%m-%d%z', @time_from ); + substr $time_str, -2, 0, ':'; + $_[0]->SUPER::set_value($time_str); + + } +} + 1; diff --git a/lib/SOAP/WSDL/XSD/Typelib/Builtin/dateTime.pm b/lib/SOAP/WSDL/XSD/Typelib/Builtin/dateTime.pm index e065c0c..b794d63 100644 --- a/lib/SOAP/WSDL/XSD/Typelib/Builtin/dateTime.pm +++ b/lib/SOAP/WSDL/XSD/Typelib/Builtin/dateTime.pm @@ -3,6 +3,8 @@ use strict; use warnings; use Class::Std::Storable; use base qw(SOAP::WSDL::XSD::Typelib::Builtin::anySimpleType); +use Date::Parse; +use Date::Format; my %pattern_of :ATTR(:name :default<()>); my %enumeration_of :ATTR(:name :default<()>); @@ -30,8 +32,31 @@ BEGIN { } return $self; }; - } - - + +sub set_value { + # use set_value from base class if we have a XML-DateTime format + #2037-12-31T00:00:00.0000000+01:00 + if ( + $_[1] =~ m{ ^\d{4} \- \d{2} \- \d{2} + T \d{2} \: \d{2} \: \d{2} (:? \. \d{1,7} )? + [\+\-] \d{2} \: \d{2} $ + }xms + ) { + $_[0]->SUPER::set_value($_[1]) + } + # use a combination of strptime and strftime for converting the date + # Unfortunately, strftime outputs the time zone as [+-]0000, whereas XML + # whants it as [+-]00:00 + # We leave out the optional nanoseconds part, as it would always be empty. + else { + # strptime sets empty values to undef - and strftime doesn't like that... + my @time_from = map { ! defined $_ ? 0 : $_ } strptime($_[1]); + my $time_str = strftime( '%Y-%m-%dT%H:%M:%S%z', @time_from ); + substr $time_str, -2, 0, ':'; + $_[0]->SUPER::set_value($time_str); + + } +} + 1; diff --git a/lib/SOAP/WSDL/XSD/Typelib/Builtin/time.pm b/lib/SOAP/WSDL/XSD/Typelib/Builtin/time.pm index 87edec2..91e2b0c 100644 --- a/lib/SOAP/WSDL/XSD/Typelib/Builtin/time.pm +++ b/lib/SOAP/WSDL/XSD/Typelib/Builtin/time.pm @@ -1,6 +1,8 @@ package SOAP::WSDL::XSD::Typelib::Builtin::time; use strict; use warnings; +use Date::Parse; +use Date::Format; my %pattern_of :ATTR(:name :default<()>); my %enumeration_of :ATTR(:name :default<()>); @@ -30,4 +32,30 @@ BEGIN { }; } +sub set_value { + # use set_value from base class if we have a XML-Time format + # 00:00:00.0000000+01:00 + if ( + $_[1] =~ m{ ^ \d{2} \: \d{2} \: \d{2} (:? \. \d{1,7} )? + [\+\-] \d{2} \: \d{2} $ + }xms + ) { + $_[0]->SUPER::set_value($_[1]) + } + # use a combination of strptime and strftime for converting the date + # Unfortunately, strftime outputs the time zone as [+-]0000, whereas XML + # whants it as [+-]00:00 + # We leave out the optional nanoseconds part, as it would always be empty. + else { + # strptime sets empty values to undef - and strftime doesn't like that... + # we even need to set it to 1 to prevent a "Day '0' out of range 1..31" warning... + my @time_from = map { ! defined $_ ? 1 : $_ } strptime($_[1]); + my $time_str = strftime( '%H:%M:%S%z', @time_from ); + substr $time_str, -2, 0, ':'; + $_[0]->SUPER::set_value($time_str); + + } +} + + 1; diff --git a/lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm b/lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm index 4959551..2353691 100644 --- a/lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm +++ b/lib/SOAP/WSDL/XSD/Typelib/ComplexType.pm @@ -29,46 +29,70 @@ sub _factory { my $type = $CLASSES_OF{ $class }->{ $name } or die "No class given for $name"; + # require all types here - this is a slow check - + # TODO speedup $type->isa('UNIVERSAL') or eval "require $type" or croak $@; + + my $is_list = $type->isa('SOAP::WSDL::XSD::Typelib::Builtin::list'); *{ "$class\::set_$name" } = sub { my ($self, $value) = @_; + +=pod + +=for developers + +The structure below looks rather weird, but is optimized for performance. + +We could use sub calls for sure, but these are much slower. And the logic +is not that easy: - # we accept: - # a) objects - # b) scalars - # c) list refs - # d) hash refs - # e) mixed stuff of all of the above, so we have to - # set our element to - # a) value if it's an object - # b) New object with value for simple values - # c 1) New object with value for list values and list type - # c 2) List ref of new objects with value for list values and non-list type - # c + e) List ref of objects for list values (list of objects) and non-list type - # d) New object with values passed to new for HASH references - # - # Die on non-ARRAY/HASH references - if you can define semantics - # for GLOB references, feel free to add them. - $attribute_ref->{ ident $self } = blessed $value - ? $value - : ref $value - ? ref $value eq 'ARRAY' - ? $type->isa('SOAP::WSDL::XSD::Typelib::Builtin::list') - ? $type->new({ value => $value }) - : [ map { - blessed($_) - ? ($_->isa('SOAP::WSDL::XSD::Typelib::Builtin::anyType')) - ? $_ - : croak 'cannot use non-XSD object as value' - : $type->new({ value => $_ }) - } @{ $value } - ] - : ref $value eq 'HASH' + we accept: + a) objects + b) scalars + c) list refs + d) hash refs + e) mixed stuff of all of the above, so we have to set our element to + a) value if it's an object + b) New object of expected class with value for simple values + c 1) New object with value for list values and list type + c 2) List ref of new objects with value for list values and non-list type + c + e 1) List ref of objects for list values (list of objects) and non-list type + c + e 2) List ref of new objects for list values (list of hashes) and non-list type + where the hash ref is passed to new as argument + d) New object with values passed to new for HASH references + + We throw an error on + a) list refs of list refs - don't know what to do with this (maybe use for lists of list types ?) + b) wrong object types + c) non-blessed non-ARRAY/HASH references - if you can define semantics + for GLOB references, feel free to add them. + d) we should also die for non-blessed non-ARRAY/HASH references in lists but don't do yet - oh my ! + +=cut + + my $is_ref = ref $value; + $attribute_ref->{ ident $self } = ($is_ref) + ? $is_ref eq 'ARRAY' + ? $is_list + ? $type->new({ value => $value }) + : [ map { + ref $_ + ? ref $_ eq 'HASH' + ? $type->new($_) + : ref $_ eq $type + ? $_ + : croak "cannot use " . ref($_) . " reference as value for $name - $type required" + : $type->new({ value => $_ }) + } @{ $value } + ] + : $is_ref eq 'HASH' ? $type->new( $value ) - : die 'Cannot use non-ARRAY/HASH as data' + : $is_ref eq $type + ? $value + : die 'Cannot use non-ARRAY/HASH as data' : $type->new({ value => $value }); }; @@ -176,7 +200,7 @@ __END__ =head1 NAME -SOAP::WSDL::XSD::Typelib::ComplexType - ComplexType XML Schema definitions +SOAP::WSDL::XSD::Typelib::ComplexType - complexType base class =head1 Bugs and limitations diff --git a/lib/SOAP/WSDL/XSD/Typelib/Element.pm b/lib/SOAP/WSDL/XSD/Typelib/Element.pm index ca6bbb5..77ccd31 100644 --- a/lib/SOAP/WSDL/XSD/Typelib/Element.pm +++ b/lib/SOAP/WSDL/XSD/Typelib/Element.pm @@ -63,27 +63,71 @@ sub end_tag { 1; =pod + +=head1 NAME + +SOAP::WSDL::XSD::Typelib::Element - element base clase =head1 SYNOPSIS + +This example creates a class for this XML schema definition: + + package MyElement; use strict; use Class::Std::Storable; use base ( - 'SOAP::WSDL::XSD::Typelib::Element', + 'SOAP::WSDL::XSD::Typelib::Element', + 'SOAP::WSDL::XSD::Typelib::Builtin::string', ); __PACKAGE__->__set_name('MyElementName'); __PACKAGE__->__set_nillable(1); __PACKAGE__->__set_minOccurs(1); __PACKAGE__->__set_maxOccurs(1); - __PACKAGE__->__set_ref(1); + __PACKAGE__->__set_ref(0); + +Now we create this XML schema definition type class: + + + + package MyElement2; + use strict; + use Class::Std::Storable; + use base ( + 'SOAP::WSDL::XSD::Typelib::Element', + 'MyElement' + ); + __PACKAGE__->__set_name('MyElementName'); + __PACKAGE__->__set_nillable(0); + __PACKAGE__->__set_ref(1); + +=head1 NOTES + +=over + +=item * type="Foo" + +Implemented via inheritance. + +=item * ref="Foo" + +Implemented via inheritance, too. Calling + +__PACKAGE__->__set_ref(1) is highly encouraged, though it has no +effect yet - it will probably be needed for serialization to XML +Schema definitions some day. + +=back + =head1 BUGS AND LIMITATIONS =over -=item * minOccurs maxOccurs ref not implemented +=item * minOccurs maxOccurs not implemented These attributes are not yet supported, though they may be set as class properties via __PACKAGE__->__set_FOO methods. diff --git a/lib/SOAP/WSDL/XSD/Typelib/SimpleType.pm b/lib/SOAP/WSDL/XSD/Typelib/SimpleType.pm index 74cc0e0..d7cb67f 100644 --- a/lib/SOAP/WSDL/XSD/Typelib/SimpleType.pm +++ b/lib/SOAP/WSDL/XSD/Typelib/SimpleType.pm @@ -17,7 +17,7 @@ __END__ =head1 NAME -SOAP::WSDL::XSD::Typelib::SimpleType - simple type base class +SOAP::WSDL::XSD::Typelib::SimpleType - simpleType base class =head1 DESCRIPTION diff --git a/t/001_use.t b/t/001_use.t index e66d375..b5a8e97 100644 --- a/t/001_use.t +++ b/t/001_use.t @@ -16,7 +16,7 @@ my @modules = qw( SOAP::WSDL::PortType SOAP::WSDL::Types SOAP::WSDL::SAX::WSDLHandler - SOAP::WSDL::XSD::Primitive + SOAP::WSDL::XSD::Builtin SOAP::WSDL::XSD::ComplexType SOAP::WSDL::XSD::SimpleType SOAP::WSDL::XSD::Element diff --git a/t/002_sax.t b/t/002_sax.t index addf232..a0fbb9f 100644 --- a/t/002_sax.t +++ b/t/002_sax.t @@ -174,7 +174,7 @@ sub xml targetNamespace="urn:myNamespace" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="urn:myNamespace" - xmlns:xsd="http://www.w3c.org/2001/XMLSchema" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" > diff --git a/t/006_client.t b/t/006_client.t index 2a27789..489f12d 100644 --- a/t/006_client.t +++ b/t/006_client.t @@ -37,7 +37,7 @@ $pod->output_string( \$output ); $pod->parse_string_document( $soap->explain() ); # print $output; - + SKIP: { skip_without_test_xml(); is_xml( $soap->call( 'EnqueueMessage' , EnqueueMessage => { @@ -48,10 +48,6 @@ SKIP: { } ) , q{ mailto:test@example.com diff --git a/t/008_client_wsdl_complexType.t b/t/008_client_wsdl_complexType.t index 071afd9..4b26c45 100644 --- a/t/008_client_wsdl_complexType.t +++ b/t/008_client_wsdl_complexType.t @@ -15,7 +15,7 @@ my $soap = SOAP::WSDL->new( wsdl => 'file:///' . $path .'/t/acceptance/wsdl/008_complexType.wsdl' )->wsdlinit(); -my $wsdl = $soap->{ _WSDL }->{ wsdl_definitions }; +my $wsdl = $soap->get_definitions; my $schema = $wsdl->first_types(); my $type = $schema->find_type('Test' , 'testComplexTypeAll'); my $element = $type->get_element()->[0]; diff --git a/t/018_generator.t b/t/018_generator.t new file mode 100644 index 0000000..b390be0 --- /dev/null +++ b/t/018_generator.t @@ -0,0 +1,30 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use Test::More tests => 1; +use lib '../lib'; +use XML::LibXML; +use SOAP::WSDL::SAX::WSDLHandler; +use SOAP::WSDL::SAX::MessageHandler; +use File::Path; +use File::Basename; + +my $path = dirname __FILE__; + +my $filter = SOAP::WSDL::SAX::WSDLHandler->new(); +my $parser = XML::LibXML->new(); +$parser->set_handler( $filter ); +$parser->parse_file( "$path/../example/wsdl/globalweather.xml" ); + +my $wsdl; +ok( $wsdl = $filter->get_data() , "get object tree"); + +$wsdl->create({ + base_path => "$path/testlib", + typemap_prefix => "Test::Typemap::", + type_prefix => "Test::Type::", + element_prefix => "Test::Element::", + interface_prefix => "Test::Interface::", +}); + +rmtree "$path/testlib"; \ No newline at end of file diff --git a/t/098_pod.t b/t/098_pod.t index 4fbcfb0..82a4975 100644 --- a/t/098_pod.t +++ b/t/098_pod.t @@ -2,26 +2,18 @@ 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/', '../bin/'); +# perl Build test or make test run from top-level dir. +if ( -d '../t/' ) { + @directories = ('../lib/'); } -else -{ - @directories = (); +else { + @directories = (); # empty - will work automatically } -my @files = all_pod_files( - @directories -); +my @files = all_pod_files(@directories); plan tests => scalar(@files); -foreach my $module (@files) -{ +foreach my $module (@files){ pod_file_ok( $module ) } \ No newline at end of file diff --git a/t/SOAP/WSDL/02_port.t b/t/SOAP/WSDL/02_port.t index b08f6b0..7671034 100644 --- a/t/SOAP/WSDL/02_port.t +++ b/t/SOAP/WSDL/02_port.t @@ -1,4 +1,4 @@ -use Test::More tests => 10; +use Test::More tests => 7; use strict; use warnings; use diagnostics; @@ -26,22 +26,10 @@ ok( $soap = SOAP::WSDL->new( wsdl => 'file:///' . $path . '/acceptance/wsdl/' . $name . '.wsdl' ), 'Instantiated object' ); -ok( $soap->wsdlinit(), 'parsed WSDL' ); - ok( ($soap->servicename('testService') ), 'set service' ); -ok( ($soap->portname('testPort') ) ,'set portname'); - -ok( ($soap->portname() eq 'testPort' ), -"Found first port definition" ); - -ok( ($soap->portname('testPort2') ), -"Found second port definition (based on URL)" ); - -ok( ($soap->portname('testPort3') ), -"Found third port definition (based on Name)" ); +ok( ($soap->portname('testPort2') ) ,'set portname'); +ok( $soap->wsdlinit(), 'parsed WSDL' ); ok( $soap->wsdlinit( servicename => 'testService', portname => 'testPort'), 'parsed WSDL' ); -$soap->_wsdl_init_methods(); - ok( ($soap->portname() eq 'testPort' ), 'found port passed to wsdlinit'); \ No newline at end of file diff --git a/t/SOAP/WSDL/03_complexType-all.t b/t/SOAP/WSDL/03_complexType-all.t index b33a3e5..3c6278d 100644 --- a/t/SOAP/WSDL/03_complexType-all.t +++ b/t/SOAP/WSDL/03_complexType-all.t @@ -59,14 +59,12 @@ TODO: { eval { - $xml = $soap->serializer->method( - $soap->call('test', + $xml = $soap->call('test', testAll => { Test1 => 'Test 1', Test2 => [ 'Test 2', 'Test 3' ] } - ) - ); + ); }; ok( ($@ =~m/illegal\snumber\sof\selements/), @@ -74,13 +72,11 @@ ok( ($@ =~m/illegal\snumber\sof\selements/), ); eval { - $xml = $soap->serializer->method( - $soap->call('test', + $xml = $soap->call('test', testAll => { Test1 => 'Test 1', } - ) - ) + ); }; ok($@, 'Died on illegal number of elements (not enough)'); diff --git a/t/SOAP/WSDL/03_complexType-sequence.t b/t/SOAP/WSDL/03_complexType-sequence.t index a48385c..1afbb24 100644 --- a/t/SOAP/WSDL/03_complexType-sequence.t +++ b/t/SOAP/WSDL/03_complexType-sequence.t @@ -35,8 +35,6 @@ ok( $soap->wsdlinit( servicename => 'testService', ), 'parsed WSDL' ); $soap->no_dispatch(1); -$soap->serializer()->envprefix('SOAP-ENV'); -$soap->serializer()->encprefix('SOAP-ENC'); #4 ok $xml = $soap->call('test', @@ -46,33 +44,32 @@ ok $xml = $soap->call('test', } ), 'Serialized complexType'; -#5 -eval -{ - $xml = $soap->serializer->method( - $soap->call('test', - testSequence => { - Test1 => 'Test 1', - } - ) - ); -}; -ok( ($@), - "Died on illegal number of elements" -); - -#6 -eval -{ - $xml = $soap->serializer->method( - $soap->call('test', - testSequence => { - Test1 => 'Test 1', - Test2 => [ 1, 2, 3, ] - } - ) - ); -}; -ok( ($@), - "Died on illegal number of elements" -); +TODO: { + local $TODO = "not implemented yet"; + #5 + eval + { + $xml = $soap->call('test', + testSequence => { + Test1 => 'Test 1', + } + ); + }; + ok( ($@), + "Died on illegal number of elements" + ); + + #6 + eval + { + $xml = $soap->call('test', + testSequence => { + Test1 => 'Test 1', + Test2 => [ 1, 2, 3, ] + } + ); + }; + ok( ($@), + "Died on illegal number of elements" + ); +}; \ No newline at end of file diff --git a/t/SOAP/WSDL/XSD/Typelib/Builtin/002_dateTime.t b/t/SOAP/WSDL/XSD/Typelib/Builtin/002_dateTime.t new file mode 100644 index 0000000..cd6a993 --- /dev/null +++ b/t/SOAP/WSDL/XSD/Typelib/Builtin/002_dateTime.t @@ -0,0 +1,25 @@ +use Test::More tests => 2; +use strict; +use warnings; +use lib '../lib'; + +use_ok('SOAP::WSDL::XSD::Typelib::Builtin::dateTime'); +my $obj; + +$obj = SOAP::WSDL::XSD::Typelib::Builtin::dateTime->new(); + +$obj->set_value( '2037-12-31' ); + +is $obj->get_value() , '2037-12-31T00:00:00+01:00', 'conversion'; + +$obj->set_value('2037-12-31T00:00:00.0000000+01:00'); + + + +# exit; + +#~ use Benchmark; +#~ timethese 10000, { + #~ xml => sub { $obj->set_value('2037-12-31T00:00:00.0000000+01:00') }, + #~ string => sub { $obj->set_value('2037-12-31') }, +#~ } \ No newline at end of file diff --git a/t/SOAP/WSDL/XSD/Typelib/Builtin/002_time.t b/t/SOAP/WSDL/XSD/Typelib/Builtin/002_time.t new file mode 100644 index 0000000..fe28fcb --- /dev/null +++ b/t/SOAP/WSDL/XSD/Typelib/Builtin/002_time.t @@ -0,0 +1,25 @@ +use Test::More tests => 3; +use strict; +use warnings; +use lib '../lib'; + +use_ok('SOAP::WSDL::XSD::Typelib::Builtin::time'); +my $obj; + +$obj = SOAP::WSDL::XSD::Typelib::Builtin::time->new(); + +$obj->set_value( '12:23:03' ); +is $obj->get_value() , '12:23:03+01:00', 'conversion'; + +$obj->set_value( '12:23:03.12345+01:00' ), ; +is $obj->get_value() , '12:23:03.12345+01:00', 'no conversion'; + + + +# exit; + +#~ use Benchmark; +#~ timethese 10000, { + #~ xml => sub { $obj->set_value('2037-12-31T00:00:00.0000000+01:00') }, + #~ string => sub { $obj->set_value('2037-12-31') }, +#~ } \ No newline at end of file diff --git a/t/SOAP/WSDL/XSD/Typelib/Builtin/003_date.t b/t/SOAP/WSDL/XSD/Typelib/Builtin/003_date.t new file mode 100644 index 0000000..56602c3 --- /dev/null +++ b/t/SOAP/WSDL/XSD/Typelib/Builtin/003_date.t @@ -0,0 +1,18 @@ +use Test::More tests => 3; +use strict; +use warnings; +use lib '../lib'; + +use_ok('SOAP::WSDL::XSD::Typelib::Builtin::date'); +my $obj; + +$obj = SOAP::WSDL::XSD::Typelib::Builtin::date->new(); + +$obj->set_value( '2037/12/31' ); + +is $obj->get_value() , '2037-12-31+01:00', 'conversion'; + + +$obj->set_value( '2037-12-31' ); +is $obj->get_value() , '2037-12-31', 'no conversion on match'; +