File Coverage

lib/XML/Compile/SOAP12.pm
Criterion Covered Total %
statement 30 204 14.7
branch 0 78 0.0
condition 0 25 0.0
subroutine 10 34 29.4
pod 6 10 60.0
total 46 351 13.1


line stmt bran cond sub pod time code
1             # Copyrights 2009-2018 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution XML-Compile-SOAP12. Meta-POD processed
6             # with OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package XML::Compile::SOAP12;
10 1     1   75529 use vars '$VERSION';
  1         2  
  1         60  
11             $VERSION = '3.06';
12              
13 1     1   6 use base 'XML::Compile::SOAP';
  1         3  
  1         631  
14              
15 1     1   116989 use warnings;
  1         2  
  1         27  
16 1     1   5 use strict;
  1         2  
  1         23  
17              
18 1     1   5 use Log::Report 'xml-compile-soap12', syntax => 'SHORT';
  1         2  
  1         9  
19              
20 1     1   289 use XML::Compile::Util qw/pack_type unpack_type XMLNS type_of_node/;
  1         2  
  1         72  
21 1     1   7 use XML::Compile::SOAP::Util qw/WSDL11SOAP12/;
  1         3  
  1         61  
22              
23 1     1   505 use XML::Compile::SOAP12::Util;
  1         3  
  1         112  
24 1     1   432 use XML::Compile::SOAP12::Operation;
  1         3  
  1         47  
25              
26 1     1   7 use File::Glob qw(bsd_glob);
  1         2  
  1         2657  
27              
28             my %roles =
29             ( NEXT => SOAP12NEXT
30             , NONE => SOAP12NONE
31             , ULTIMATE => SOAP12ULTIMATE
32             );
33             my %rev_roles = reverse %roles;
34              
35             __PACKAGE__->register
36             ( WSDL11SOAP12
37             , &SOAP12ENV => 'XML::Compile::SOAP12::Operation'
38             );
39              
40              
41             sub new($@)
42 0     0 1   { my $class = shift;
43 0           (bless {}, $class)->init( {@_} );
44             }
45              
46             sub init($)
47 0     0 0   { my ($self, $args) = @_;
48 0           $self->SUPER::init($args);
49 0           $self->_initSOAP12($self->schemas);
50             }
51              
52             sub _initSOAP12($)
53 0     0     { my ($thing, $schemas) = @_;
54 0           $thing->_initSOAP($schemas);
55              
56             return $thing
57 0 0         if $schemas->{did_init_SOAP12}++; # ugly
58              
59 0           $schemas->addPrefixes
60             ( env12 => SOAP12ENV # preferred names by spec
61             , enc12 => SOAP12ENC
62             , rpc12 => SOAP12RPC
63             );
64              
65 0           (my $dir = __FILE__) =~ s!.pm$!/xsd!;
66 0           my @xsd = bsd_glob "$dir/*";
67 0           $schemas->importDefinitions(\@xsd);
68              
69 0           $schemas->importDefinitions(XMLNS, element_form_default => 'qualified'
70             , attribute_form_default => 'qualified');
71 0           $thing;
72             }
73              
74             sub _initWSDL11($)
75 0     0     { my ($class, $wsdl) = @_;
76              
77 0           trace "initialize SOAP12 operations for WSDL11";
78 0           $class->_initSOAP12($wsdl);
79              
80 0           $wsdl->addPrefixes(soap12 => WSDL11SOAP12);
81 0           $wsdl->addKeyRewrite('PREFIXED(soap12)');
82              
83 0           (my $xsd = __FILE__) =~ s!SOAP12.pm$!WSDL11/xsd/wsdl-soap12.xsd!;
84 0           $wsdl->importDefinitions($xsd, element_form_default => 'qualified');
85              
86 0           $wsdl->declare(READER =>
87             [ "soap12:address", "soap12:operation", "soap12:binding"
88             , "soap12:body", "soap12:header", "soap12:fault" ]);
89             }
90              
91 0     0 1   sub version { 'SOAP12' }
92 0     0 0   sub envelopeNS { SOAP12ENV }
93 0     0 0   sub envType($) { pack_type SOAP12ENV, $_[1] }
94              
95             #---------------
96              
97             #-----------------------------------
98              
99             sub sender($)
100 0     0 0   { my ($self, $args) = @_;
101              
102             error __x"headerfault does only exist in SOAP1.1"
103 0 0         if $args->{header_fault};
104              
105 0           $self->SUPER::sender($args);
106             }
107              
108              
109             sub compileMessage($$)
110 0     0 1   { my ($self, $direction, %args) = @_;
111 0   0       $args{style} ||= 'document';
112              
113 0 0         if(ref $args{body} eq 'ARRAY')
114 0           { my @h = @{$args{body}};
  0            
115 0           my @parts;
116 0           push @parts, +{name => shift @h, element => shift @h} while @h;
117 0           $args{body} = +{use => 'literal', parts => \@parts};
118             }
119              
120 0 0         if(ref $args{header} eq 'ARRAY')
121 0           { my @h = @{$args{header}};
  0            
122 0           my @o;
123 0           while(@h)
124 0           { my $part = +{name => shift @h, element => shift @h};
125 0           push @o, +{use => 'literal', parts => [$part]};
126             }
127 0           $args{header} = \@o;
128             }
129              
130 0           my $f = $args{faults};
131 0 0         if(ref $f eq 'ARRAY')
132 0           { $args{faults} = +{};
133 0           my @f = @$f;
134 0           while(@f)
135 0           { my $name = shift @f;
136 0           my $part = +{name => $name, element => shift @f};
137 0           $args{faults}{$name} = +{use => 'literal', part => $part};
138             }
139             }
140              
141 0           $self->SUPER::compileMessage($direction, %args);
142             }
143              
144             #------------------------------------------------
145             # Sender
146              
147             sub _sender(@)
148 0     0     { my ($self, %args) = @_;
149              
150             ### merge info into headers
151             # do not destroy original of args
152 0 0         my %destination = @{$args{destination} || []};
  0            
153              
154 0           my $understand = $args{mustUnderstand};
155 0 0         my %understand = map +($_ => 1),
    0          
156             ref $understand eq 'ARRAY' ? @$understand
157             : defined $understand ? $understand : ();
158              
159 0 0         foreach my $h ( @{$args{header} || []} )
  0            
160 0           { my $part = $h->{parts}[0];
161 0           my $label = $part->{name};
162 0   0       $part->{mustUnderstand} ||= delete $understand{$label};
163 0   0       $part->{destination} ||= delete $destination{$label};
164             }
165              
166 0 0         if(keys %understand)
167 0           { error __x"mustUnderstand for unknown header {headers}"
168             , headers => [keys %understand];
169             }
170              
171 0 0         if(keys %destination)
172 0           { error __x"destination for unknown header {headers}"
173             , headers => [keys %destination];
174             }
175              
176             # faults are always possible
177 0 0         my @bparts = @{$args{body}{parts} || []};
  0            
178             my $w = $self->schemas->writer('env12:Fault'
179 0 0   0     , include_namespaces => sub {$_[0] ne SOAP12ENV && $_[2]}
180 0           );
181 0           push @bparts,
182             { name => 'Fault'
183             , element => pack_type(SOAP12ENV, 'Fault')
184             , writer => $w
185             };
186 0           local $args{body}{parts} = \@bparts;
187              
188 0           $self->SUPER::_sender(%args);
189             }
190              
191             sub _writer_header($)
192 0     0     { my ($self, $args) = @_;
193 0           my ($rules, $hlabels) = $self->SUPER::_writer_header($args);
194              
195 0           my $header = $args->{header};
196 0           my @rules;
197 0 0         foreach my $h (@{$header || []})
  0            
198 0           { my $part = $h->{parts}[0];
199 0           my $label = $part->{name};
200 0 0         $label eq shift @$rules or panic;
201 0           my $code = shift @$rules;
202              
203             my $understand
204             = $part->{mustUnderstand} ? 'true'
205 0 0         : defined $part->{mustUnderstand} ? 'false' # explicit
    0          
206             : undef;
207              
208 0           my $actor = $part->{destination};
209 0 0         if(ref $actor eq 'ARRAY')
    0          
210 0           { $actor = join ' ', map $self->roleURI($_), @$actor }
211             elsif(defined $actor)
212 0           { $actor =~ s/\b(\S+)\b/$self->roleURI($1)/ge }
  0            
213              
214 0           my $envpref = $self->schemas->prefixFor(SOAP12ENV);
215             my $wcode = $understand || $actor
216             ? sub
217 0     0     { my ($doc, $v) = @_;
218 0           my $xml = $code->($doc, $v);
219 0 0         $xml->setAttribute("$envpref:mustUnderstand" => 'true')
220             if defined $understand;
221 0 0         $xml->setAttribute("$envpref:actor" => $actor)
222             if $actor;
223 0           $xml;
224             }
225 0 0 0       : $code;
226              
227 0           push @rules, $label => $wcode;
228             }
229              
230 0           (\@rules, $hlabels);
231             }
232              
233             sub _writer_faults($)
234 0     0     { my ($self, $args) = @_;
235 0   0       my $faults = $args->{faults} ||= {};
236              
237 0           my (@rules, @flabels);
238              
239             # Include all namespaces in Fault, because we have no idea which namespace
240             # is used for the error code. It automatically defines everything
241             # which may be used in the detail block.
242             my $wrfault = $self->_writer('env12:Fault'
243 0     0     , include_namespaces => sub {$_[0] ne SOAP12ENV});
  0            
244              
245 0           while(my ($name, $fault) = each %$faults)
246 0           { my $part = $fault->{part};
247 0           my ($label, $type) = ($part->{name}, $part->{element});
248              
249             # spec says: details ALWAYS namespace qualified!
250             my $details = $self->_writer($type, elements_qualified => 'TOP'
251 0 0   0     , include_namespaces => sub {$_[0] ne SOAP12ENV && $_[2]});
  0            
252              
253             my $code = sub
254 0     0     { my ($doc, $data) = (shift, shift);
255 0           my %copy = %$data;
256 0   0       $copy{Role} ||= $self->roleURI($copy{faultactor});
257 0   0       my $det = delete $copy{Detail} || delete $copy{detail};
258 0 0         my @det = !defined $det ? () : ref $det eq 'ARRAY' ? @$det : $det;
    0          
259 0           $copy{Detail}{$type} = [ map $details->($doc, $_), @det ];
260 0           $wrfault->($doc, \%copy);
261 0           };
262              
263 0           push @rules, $name => $code;
264 0           push @flabels, $name;
265             }
266              
267 0           (\@rules, \@flabels);
268             }
269              
270             ##########
271             # Receiver
272              
273             sub _reader_fault_reader()
274 0     0     { my $self = shift;
275              
276             # Nasty, nasty: the spec requires name-space qualified on details,
277             # even when the schema does not specify that.
278 0           my $schemas = $self->schemas;
279             my $x = sub {
280 0     0     my ($xml, $reader, $path, $tag, $r) = @_;
281 0           my @childs = grep $_->isa('XML::LibXML::Element'), $xml->childNodes;
282 0 0         @childs or return ();
283              
284 0           my %h;
285 0           foreach my $node (@childs)
286 0           { my $type = type_of_node($node);
287 0           push @{$h{_ELEMENT_ORDER}}, $type;
  0            
288 0           $h{$type} = $schemas->reader($type, elements_qualified=>'TOP')
289             ->($node);
290             }
291 0           ($tag => \%h);
292 0           };
293              
294 0           [ Fault => pack_type(SOAP12ENV, 'Fault')
295             , $self->schemas->reader('env12:Fault'
296             , hooks => { type => 'env12:detail', replace => $x } )
297             ];
298             }
299              
300             sub _reader_faults($$)
301 0     0     { my ($self, $args, $faults) = @_;
302              
303 0           my %names;
304 0           while(my ($name, $def) = each %$faults)
305 0           { $names{$def->{part}{element}} = $name;
306             }
307              
308             sub
309 0     0     { my $data = shift;
310 0 0         my $faults = $data->{Fault} or return;
311              
312             #use Data::Dumper;
313             #warn Dumper $data;
314 0           my $code = $faults->{Code};
315 0           my ($code_ns, $code_err) = unpack_type $code->{Value};
316              
317 0           my @subcode;
318 0           for(my $sc = $code->{Subcode}; $sc; $sc = $sc->{Subcode})
319 0           { push @subcode, $sc->{Value};
320             }
321            
322             my %nice =
323             ( code => ($subcode[0] || $code_err)
324             , class => [ $code_ns, $code_err, @subcode ]
325             , reason => $faults->{Reason}{Text}[0]{_}
326 0   0       );
327              
328             $nice{role} = $self->roleAbbreviation($faults->{Role})
329 0 0         if $faults->{Role};
330              
331 0           my $details = $faults->{Detail};
332 0 0         my $dettype = $details ? delete $details->{_ELEMENT_ORDER} : undef;
333              
334             #XXX MO may need more work
335 0           my $name;
336 0 0 0       if(!$details) { $name = 'error' }
  0 0          
    0          
337             elsif(@$dettype && $names{$dettype->[0]})
338             { # fault named in WSDL
339 0           $name = $names{$dettype->[0]};
340 0 0         if(keys %$details==1)
341 0           { my (undef, $v) = %$details;
342 0 0         if(ref $v eq 'HASH') { @nice{keys %$v} = values %$v }
  0            
343 0           else { $nice{details} = $v }
344             }
345             }
346             elsif(keys %$details==1)
347             { # simple generic fault, not in WSDL. Maybe internal server error
348 0           ($name) = keys %$details;
349 0           my $v = $details->{$name};
350 0 0         my @v = ref $v eq 'ARRAY' ? @$v : $v;
351 0 0         my @r = map { UNIVERSAL::isa($_, 'XML::LibXML::Node')
  0            
352             ? $_->textContent : $_} @v;
353 0 0         $nice{$name} = @r==1 ? $r[0] : \@r;
354             }
355             else
356             { # unknown complex generic error
357 0           $name = 'generic';
358             }
359              
360 0           $data->{$name} = \%nice;
361 0           $faults->{_NAME} = $name;
362 0           $data;
363 0           };
364             }
365              
366             sub replyMustUnderstandFault($)
367 0     0 1   { my ($self, $type) = @_;
368              
369 0           +{ Fault =>
370             { Code => {Value => pack_type(SOAP12ENV, 'MustUnderstand') }
371             , Reason => {Text => {lang => 'en', _ => "SOAP mustUnderstand $type"}}
372             }
373             };
374             }
375              
376 0 0   0 1   sub roleURI($) { $roles{$_[1]} || $_[1] }
377              
378 0 0   0 1   sub roleAbbreviation($) { $rev_roles{$_[1]} || $_[1] }
379              
380             1;