File Coverage

lib/XML/Compile/SOAP11/Operation.pm
Criterion Covered Total %
statement 36 227 15.8
branch 0 100 0.0
condition 0 83 0.0
subroutine 12 27 44.4
pod 8 9 88.8
total 56 446 12.5


line stmt bran cond sub pod time code
1             # Copyrights 2007-2019 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-SOAP. 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::SOAP11::Operation;
10 7     7   50 use vars '$VERSION';
  7         13  
  7         376  
11             $VERSION = '3.26';
12              
13 7     7   43 use base 'XML::Compile::SOAP::Operation';
  7         11  
  7         2846  
14              
15 7     7   49 use warnings;
  7         15  
  7         162  
16 7     7   37 use strict;
  7         22  
  7         144  
17              
18 7     7   31 use Log::Report 'xml-compile-soap';
  7         13  
  7         29  
19              
20 7     7   1691 use List::Util 'first';
  7         15  
  7         424  
21              
22 7     7   41 use XML::Compile::Util qw/pack_type unpack_type/;
  7         13  
  7         300  
23 7     7   42 use XML::Compile::SOAP::Util qw/:soap11/;
  7         12  
  7         664  
24 7     7   488 use XML::Compile::SOAP11::Client;
  7         12  
  7         168  
25 7     7   3531 use XML::Compile::SOAP11::Server;
  7         17  
  7         242  
26 7     7   2589 use XML::Compile::SOAP::Extension;
  7         18  
  7         270  
27              
28 7     7   45 use vars '$VERSION'; # OODoc adds $VERSION to the script
  7         11  
  7         21994  
29             $VERSION ||= '(devel)';
30              
31             # client/server object per schema class, because initiation options
32             # can be different. Class reference is key.
33             my (%soap11_client, %soap11_server);
34              
35              
36             sub init($)
37 0     0 0   { my ($self, $args) = @_;
38              
39 0           $self->SUPER::init($args);
40              
41             $self->{$_} = $args->{$_} || {}
42 0   0       for qw/input_def output_def fault_def/;
43              
44 0   0       $self->{style} = $args->{style} || 'document';
45              
46 0           XML::Compile::SOAP::Extension->soap11OperationInit($self, $args);
47 0           $self;
48             }
49              
50             sub _fromWSDL11(@)
51 0     0     { my ($class, %args) = @_;
52              
53             # Extract the SOAP11 specific information from a WSDL11 file. There are
54             # half a zillion parameters.
55             my ($p_op, $b_op, $wsdl)
56 0           = @args{ qw/port_op bind_op wsdl/ };
57              
58 0           $args{schemas} = $wsdl;
59 0           $args{endpoints} = $args{serv_port}{soap_address}{location};
60              
61 0   0       my $sop = $b_op->{soap_operation} || {};
62 0   0       $args{action} ||= $sop->{soapAction};
63              
64 0   0       my $sb = $args{binding}{soap_binding} || {};
65 0   0       $args{transport} = $sb->{transport} || 'HTTP';
66 0   0       $args{style} = $sb->{style} || 'document';
67              
68             $args{input_def} = $class->_msg_parts($wsdl, $args{name}, $args{style}
69 0           , $p_op->{wsdl_input}, $b_op->{wsdl_input});
70              
71             $args{output_def} = $class->_msg_parts($wsdl, $args{name}.'Response'
72 0           , $args{style}, $p_op->{wsdl_output}, $b_op->{wsdl_output});
73              
74             $args{fault_def}
75 0           = $class->_fault_parts($wsdl, $p_op->{wsdl_fault}, $b_op->{wsdl_fault});
76              
77 0           $class->SUPER::new(%args);
78             }
79              
80             sub _msg_parts($$$$$)
81 0     0     { my ($class, $wsdl, $opname, $style, $port_op, $bind_op) = @_;
82 0           my %parts;
83              
84 0 0         defined $port_op # communication not in two directions
85             or return ({}, {});
86              
87 0 0         if(my $body = $bind_op->{soap_body})
    0          
88 0           { my $msgname = $port_op->{message};
89 0           my @parts = $class->_select_parts($wsdl, $msgname, $body->{parts});
90              
91 0           my ($ns, $local) = unpack_type $msgname;
92 0   0       my $rpc_ns = $body->{namespace} // '';
93 0 0         $wsdl->addNicePrefix(call => $rpc_ns) if $rpc_ns;
94              
95             my $procedure
96             = $style eq 'rpc' ? pack_type($rpc_ns, $opname)
97 0 0 0       : @parts==1 && $parts[0]{type} ? $msgname
    0          
98             : $local;
99              
100 0           $parts{body} = { procedure => $procedure, %$port_op, use => 'literal'
101             , %$body, parts => \@parts };
102             }
103             elsif($port_op->{message})
104             { # missing in or :output
105 0           error __x"operation {opname} has a message in its portType but no encoding in the binding", opname => $opname;
106             }
107              
108 0   0       my $bsh = $bind_op->{soap_header} || [];
109 0 0         foreach my $header (ref $bsh eq 'ARRAY' ? @$bsh : $bsh)
110 0           { my $msgname = $header->{message};
111 0           my @parts = $class->_select_parts($wsdl, $msgname, $header->{part});
112 0           push @{$parts{header}}, { %$header, parts => \@parts };
  0            
113              
114 0 0         foreach my $fault ( @{$header->{headerfault} || []} )
  0            
115 0           { $msgname = $fault->{message};
116 0           my @hf = $class->_select_parts($wsdl, $msgname, $fault->{part});
117 0           push @{$parts{headerfault}}, { %$fault, parts => \@hf };
  0            
118             }
119             }
120 0           \%parts;
121             }
122              
123             sub _select_parts($$$)
124 0     0     { my ($class, $wsdl, $msgname, $need_parts) = @_;
125 0 0         my $msg = $wsdl->findDef(message => $msgname)
126             or error __x"cannot find message {name}", name => $msgname;
127              
128             my @need
129 0 0         = ref $need_parts ? @$need_parts
    0          
130             : defined $need_parts ? $need_parts
131             : ();
132              
133 0   0       my $parts = $msg->{wsdl_part} || [];
134 0 0         @need or return @$parts;
135              
136 0           my @sel;
137 0           my %parts = map +($_->{name} => $_), @$parts;
138 0           foreach my $name (@need)
139             { my $part = $parts{$name}
140             or error __x"message {msg} does not have a part named {part}"
141 0 0         , msg => $msg->{name}, part => $name;
142              
143 0           push @sel, $part;
144             }
145              
146 0           @sel;
147             }
148              
149             sub _fault_parts($$$)
150 0     0     { my ($class, $wsdl, $portop, $bind) = @_;
151              
152 0   0       my $port_faults = $portop || [];
153 0           my %faults;
154              
155 0           foreach my $fault (@$bind)
156 0 0         { $fault or next;
157 0           my $name = $fault->{name};
158              
159 0     0     my $port = first {$_->{name} eq $name} @$port_faults;
  0            
160 0 0         defined $port
161             or error __x"cannot find port for fault {name}", name => $name;
162              
163             my $msgname = $port->{message}
164 0 0         or error __x"no fault message name in portOperation";
165              
166 0 0         my $message = $wsdl->findDef(message => $msgname)
167             or error __x"cannot find fault message {name}", name => $msgname;
168              
169 0 0         @{$message->{wsdl_part} || []}==1
  0 0          
170             or error __x"fault message {name} must have one part exactly"
171             , name => $msgname;
172              
173             $faults{$name} =
174             { part => $message->{wsdl_part}[0]
175 0   0       , use => ($fault->{use} || 'literal')
176             };
177             }
178              
179 0           +{ faults => \%faults };
180             }
181              
182             #-------------------------------------------
183              
184              
185 0     0 1   sub style() {shift->{style}}
186             sub version() { 'SOAP11' }
187 0     0 1   sub serverClass { 'XML::Compile::SOAP11::Server' }
188 0     0 1   sub clientClass { 'XML::Compile::SOAP11::Client' }
189              
190             #-------------------------------------------
191              
192              
193             sub addHeader($$$%)
194 0     0 1   { my ($self, $dir, $label, $el, %opts) = @_;
195 0           my $elem = $self->schemas->findName($el);
196 0 0         my $defs
    0          
    0          
197             = $dir eq 'INPUT' ? 'input_def'
198             : $dir eq 'OUTPUT' ? 'output_def'
199             : $dir eq 'FAULT' ? 'fault_def'
200             : panic "addHeader $dir";
201 0   0       my $headers = $self->{$defs}{header} ||= [];
202              
203 0 0   0     if(my $already = first {$_->{part} eq $label} @$headers)
  0            
204             { # the header is already defined, ignore second declaration
205 0           my $other_type = $already->{parts}[0]{element};
206 0 0         $other_type eq $elem
207             or error __x"header {label} already defined with type {type}"
208             , label => $label, type => $other_type;
209 0           return $already;
210             }
211              
212             my %part =
213             ( part => $label, use => 'literal'
214             , parts => [
215             { name => $label, element => $elem
216             , mustUnderstand => $opts{mustUnderstand}
217             , destination => $opts{destination}
218 0           } ]);
219              
220 0           push @$headers, \%part;
221 0           \%part;
222             }
223              
224             #-------------------------------------------
225              
226              
227             sub compileHandler(@)
228 0     0 1   { my ($self, %args) = @_;
229              
230             my $soap = $soap11_server{$self->{schemas}}
231 0   0       ||= XML::Compile::SOAP11::Server->new(schemas => $self->{schemas});
232 0   0       my $style = $args{style} ||= $self->style;
233              
234 0           my @ro = (%{$self->{input_def}}, %{$self->{fault_def}});
  0            
  0            
235 0           my @so = (%{$self->{output_def}}, %{$self->{fault_def}});
  0            
  0            
236              
237 0   0       $args{encode} ||= $soap->_sender(@so, %args);
238 0   0       $args{decode} ||= $soap->_receiver(@ro, %args);
239 0   0       $args{kind} ||= $self->kind;
240 0           $args{name} = $self->name;
241 0   0       $args{selector} ||= $soap->compileFilter(%{$self->{input_def}},
  0            
242             style => $style);
243              
244             $args{callback} = XML::Compile::SOAP::Extension
245 0           ->soap11HandlerWrapper($self, $args{callback}, \%args);
246              
247 0           $soap->compileHandler(%args);
248             }
249              
250              
251             sub compileClient(@)
252 0     0 1   { my ($self, %args) = @_;
253              
254             my $client = $soap11_client{$self->{schemas}}
255 0   0       ||= XML::Compile::SOAP11::Client->new(schemas => $self->{schemas});
256 0   0       my $style = $args{style} ||= $self->style;
257 0   0       my $kind = $args{kind} ||= $self->kind;
258              
259 0           my @so = (%{$self->{input_def}}, %{$self->{fault_def}});
  0            
  0            
260 0           my @ro = (%{$self->{output_def}}, %{$self->{fault_def}});
  0            
  0            
261              
262             my $call = $client->compileClient
263             ( name => $self->name
264             , kind => $kind
265             , encode => $client->_sender(@so, %args)
266             , decode => $client->_receiver(@ro, %args)
267             , transport => $self->compileTransporter(%args)
268             , async => $args{async}
269             , soap => $args{soap}
270 0           );
271              
272 0           XML::Compile::SOAP::Extension->soap11ClientWrapper($self, $call, \%args);
273             }
274              
275             #--------------------------
276              
277              
278             my $sep = '#--------------------------------------------------------------';
279              
280             sub explain($$$@)
281 0     0 1   { my ($self, $schema, $format, $dir, %args) = @_;
282              
283             # $schema has to be passed as argument, because we do not want operation
284             # objects to be glued to a schema object after compile time.
285              
286 0 0         UNIVERSAL::isa($schema, 'XML::Compile::Schema')
287             or error __x"explain() requires first element to be a schema";
288              
289 0 0         $format eq 'PERL'
290             or error __x"only PERL template supported for the moment, not {got}"
291             , got => $format;
292              
293 0           my $style = $self->style;
294 0           my $opname = $self->name;
295 0   0       my $skip_header = delete $args{skip_header} || 0;
296 0   0       my $recurse = delete $args{recurse} || 0;
297              
298 0 0         my $def = $dir eq 'INPUT' ? $self->{input_def} : $self->{output_def};
299 0           my $faults = $self->{fault_def}{faults};
300              
301 0           my (@struct, @postproc, @attach);
302 0 0         my @main = $recurse
303             ? "# The details of the types and elements are attached below."
304             : "# To explore the HASHes for each part, use recurse option.";
305              
306             HEAD_PART:
307 0 0         foreach my $header (@{$def->{header} || []})
  0            
308 0 0         { foreach my $part ( @{$header->{parts} || []} )
  0            
309 0           { my $name = $part->{name};
310             my ($kind, $value) = $part->{type} ? (type => $part->{type})
311 0 0         : (element => $part->{element});
312            
313 0   0       my $type = $schema->prefixed($value) || $value;
314 0 0 0       push @main, ''
315             , "# Header part '$name' is $kind $type"
316             , ($kind eq 'type' && $recurse ? "# See fake element '$name'" : ())
317             , "my \$$name = {};";
318 0           push @struct, " $name => \$$name,";
319            
320 0 0         $recurse or next HEAD_PART;
321            
322 0           my $elem = $value;
323 0 0         if($kind eq 'type')
324             { # generate element with part name, because template requires elem
325 0           $schema->compileType(READER => $value, element => $name);
326 0           $elem = $name;
327             }
328            
329 0           push @attach, '', $sep, "\$$name ="
330             , $schema->template(PERL => $elem, skip_header => 1, %args), ';';
331             }
332             }
333              
334             BODY_PART:
335 0 0         foreach my $part ( @{$def->{body}{parts} || []} )
  0            
336 0           { my $name = $part->{name};
337             my ($kind, $value) = $part->{type} ? (type => $part->{type})
338 0 0         : (element => $part->{element});
339              
340 0   0       my $type = $schema->prefixed($value) || $value;
341 0 0 0       push @main, ''
342             , "# Body part '$name' is content for $kind $type"
343             , ($kind eq 'type' && $recurse ? "# See fake element '$name'" : ())
344             , "my \$$name = {};";
345 0           push @struct, " $name => \$$name,";
346              
347 0 0         $recurse or next BODY_PART;
348              
349 0           my $elem = $value;
350 0 0         if($kind eq 'type')
351             { # generate element with part name, because template requires elem
352 0           $schema->compileType(READER => $value, element => $name);
353 0           $elem = $name;
354             }
355              
356 0           push @attach, '', $sep, "\$$name ="
357             , $schema->template(PERL => $elem, skip_header => 1, %args), ';';
358             }
359              
360 0           foreach my $fault (sort keys %$faults)
361 0           { my $part = $faults->{$fault}{part}; # fault msgs have only one part
362             my ($kind, $value) = $part->{type} ? (type => $part->{type})
363 0 0         : (element => $part->{element});
364              
365 0 0         my $type = $schema->prefixFor($value)
366             ? $schema->prefixed($value) : $value;
367              
368 0 0         if($dir eq 'OUTPUT')
369 0 0 0       { push @main, ''
370             , "# ... or fault $fault is $kind"
371             , "my \$$fault = {}; # $type"
372             , ($kind eq 'type' && $recurse ? "# See fake element '$fault'" : ())
373             , "my \$fault ="
374             , " { code => pack_type(\$myns, 'Open.NoSuchFile')"
375             , " , reason => 'because I can'"
376             , " , detail => \$$fault"
377             , ' };';
378 0           push @struct, " $fault => \$fault,";
379             }
380             else
381 0   0       { my $nice = $schema->prefixed($type) || $type;
382 0           push @postproc
383             , " elsif(\$errname eq '$fault')"
384             , " { # \$details is a $nice"
385             , " }";
386             }
387              
388 0 0         $recurse or next;
389              
390 0           my $elem = $value;
391 0 0         if($kind eq 'type')
392             { # generate element with part name, because template requires elem
393 0           $schema->compileType(READER => $value, element => $fault);
394 0           $elem = $fault;
395             }
396              
397 0           push @attach, '', $sep, "# FAULT", "\$$fault ="
398             , $schema->template(PERL => $elem, skip_header => 1, %args), ';';
399             }
400              
401 0 0         if($dir eq 'INPUT')
    0          
402 0           { push @main, ''
403             , '# Call with the combination of parts.'
404             , 'my @params = (', @struct, ');'
405             , 'my ($answer, $trace) = $call->(@params);', ''
406             , '# @params will become %$data_in in the server handler.'
407             , '# $answer is a HASH, an operation OUTPUT or Fault.'
408             , '# $trace is an XML::Compile::SOAP::Trace object.';
409              
410 0           unshift @postproc, ''
411             , '# You may get an error back from the server'
412             , 'if(my $f = $answer->{Fault})'
413             , '{ my $errname = $f->{_NAME};'
414             , ' my $error = $answer->{$errname};'
415             , ' print "$error->{code}\n";', ''
416             , ' my $details = $error->{detail};'
417             , ' if(not $details)'
418             , ' { # system error, no $details'
419             , ' }';
420            
421 0           push @postproc
422             , ' exit 1;'
423             , '}';
424             }
425             elsif($dir eq 'OUTPUT')
426 0           { s/^/ / for @main, @struct;
427 0           unshift @main, ''
428             , "sub handle_$opname(\$)"
429             , '{ my ($server, $data_in) = @_;'
430             , ' # process $data_in, structured as INPUT message.'
431             , ' # Hint: use "print Dumper $data_in"';
432              
433 0           push @main, ''
434             , ' # This will end-up as $answer at client-side'
435             , ' return # optional keyword'
436             , " +{", @struct, " };", "}";
437             }
438             else
439 0           { error __x"template for direction INPUT or OUTPUT, not {got}"
440             , got => $dir;
441             }
442              
443 0           my @header;
444 0 0         if(my $how = $def->{body})
445 0   0       { my $use = $how->{use} || 'literal';
446             push @header
447 0   0       , "# Operation ". ($how->{procedure} || '(unnamed)')
448             , "# $dir, $style $use";
449             }
450             else
451 0           { push @header,
452             , "# Operation $opname has no $dir";
453             }
454              
455 0           foreach my $fault (sort keys %$faults)
456 0           { my $usage = $faults->{$fault};
457 0           push @header
458             , "# FAULT $fault, $style $usage->{use}" # $style?
459             }
460              
461             push @header
462             , "# Produced by ".__PACKAGE__." version $VERSION"
463             , "# on ".localtime()
464             , "#"
465             , "# The output below is only an example: it cannot be used"
466             , "# without interpretation, although very close to real code."
467             , ""
468 0 0         unless $args{skip_header};
469              
470 0 0         if($dir eq 'INPUT')
471 0           { push @header
472             , '# Compile only once in your code, usually during initiation:'
473             , "# my \$call = \$wsdl->compileClient('$opname');"
474             , '# then call it as often as you need. Alternatively'
475             , '# $wsdl->compileCalls(); # once'
476             , "# \$response = \$wsdl->call('$opname', \$request);";
477             }
478             else #OUTPUT
479 0           { push @header
480             , '# As part of the initiation phase of your server:'
481             , 'my $daemon = XML::Compile::SOAP::HTTPDaemon->new;'
482             , '$daemon->operationsFromWSDL'
483             , ' ( $wsdl'
484             , ' , callbacks =>'
485             , " { $opname => \\&handle_$opname}"
486             , ' );'
487             }
488              
489 0           join "\n", @header, @main, @postproc, @attach, '';
490             }
491              
492             sub parsedWSDL()
493 0     0 1   { my $self = shift;
494             +{ input => $self->{input_def}{body}
495             , output => $self->{output_def}{body}
496             , faults => $self->{fault_def}{faults}
497 0           , style => $self->style
498             };
499             }
500              
501             1;