File Coverage

blib/lib/Lim/RPC/Protocol/SOAP.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Lim::RPC::Protocol::SOAP;
2              
3 3     3   31407 use common::sense;
  3         8  
  3         34  
4 3     3   209 use Carp;
  3         6  
  3         421  
5              
6 3     3   20 use Scalar::Util qw(blessed weaken);
  3         8  
  3         246  
7              
8 3     3   19 use HTTP::Status qw(:constants);
  3         8  
  3         3061  
9 3     3   25 use HTTP::Request ();
  3         8  
  3         74  
10 3     3   17 use HTTP::Response ();
  3         9  
  3         52  
11              
12 3     3   1474 use SOAP::Lite ();
  0            
  0            
13             use SOAP::Transport::HTTP ();
14              
15             use Lim ();
16             use Lim::RPC::Callback ();
17              
18             use base qw(Lim::RPC::Protocol);
19              
20             =encoding utf8
21              
22             =head1 NAME
23              
24             ...
25              
26             =head1 VERSION
27              
28             See L for version.
29              
30             =cut
31              
32             our $VERSION = $Lim::VERSION;
33              
34             =head1 SYNOPSIS
35              
36             ...
37              
38             =head1 SUBROUTINES/METHODS
39              
40             =head2 Init
41              
42             =cut
43              
44             sub Init {
45             }
46              
47             =head2 Destroy
48              
49             =cut
50              
51             sub Destroy {
52             my ($self) = @_;
53            
54             delete $self->{soap};
55             delete $self->{wsdl};
56             }
57              
58             =head2 name
59              
60             =cut
61              
62             sub name {
63             'soap';
64             }
65              
66             =head2 serve
67              
68             =cut
69              
70             sub serve {
71             my ($self, $module, $module_shortname) = @_;
72             my ($wsdl, $calls, $tns, $soap, $soap_name, $dispatch, $obj, $obj_class);
73            
74             $calls = $module->Calls;
75             $tns = $module.'::Server';
76             ($soap_name = $module) =~ s/:://go;
77              
78             $soap = SOAP::Transport::HTTP::Server->new;
79             $soap->serializer->ns('urn:'.$tns, 'lim1');
80             $soap->serializer->autotype(0);
81             $obj = $self->server->module_obj_by_protocol($module_shortname, $self->name);
82             $obj_class = ref($obj);
83             # TODO: check if $obj_class alread is a SOAP::Server::Parameters
84             eval "push(\@${obj_class}::ISA, 'SOAP::Server::Parameters');";
85             if ($@) {
86             die $@;
87             }
88             $dispatch = {};
89             foreach my $call (keys %$calls) {
90             $dispatch->{'urn:'.$tns.'#'.$call} = $obj;
91             }
92             $soap->dispatch_with($dispatch);
93             $self->{soap}->{$module} = $soap;
94            
95             $wsdl =
96             '
97            
98             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
99             xmlns:tns="urn:'.$tns.'"
100             xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
101             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
102             name="'.$soap_name.'"
103             targetNamespace="urn:'.$tns.'">
104              
105             ';
106              
107             # Generate types
108             $wsdl .= '
109            
110             ';
111             foreach my $call (keys %$calls) {
112             my $h = $calls->{$call};
113            
114             if (exists $h->{in}) {
115             $wsdl .= '
116            
117            
118             ';
119             $wsdl .= __wsdl_gen_complex_types($h->{in});
120             $wsdl .= '
121            
122            
123             ';
124             }
125             else {
126             $wsdl .= '
127             ';
128             }
129            
130             if (exists $h->{out}) {
131             $wsdl .= '
132            
133            
134             ';
135             $wsdl .= __wsdl_gen_complex_types($h->{out});
136             $wsdl .= '
137            
138            
139             ';
140             }
141             else {
142             $wsdl .= '
143             ';
144             }
145             }
146             $wsdl .= '
147            
148              
149             ';
150            
151             # Generate message
152             foreach my $call (keys %$calls) {
153             $wsdl .= '
154            
155            
156             ';
157             $wsdl .= '
158            
159            
160             ';
161             }
162             $wsdl .= '
163             ';
164            
165             # Generate portType
166             $wsdl .= '
167             ';
168             foreach my $call (keys %$calls) {
169             $wsdl .= '
170            
171            
172            
173             ';
174             }
175             $wsdl .= '
176              
177             ';
178            
179             # Generate binding
180             $wsdl .= '
181            
182             ';
183             foreach my $call (keys %$calls) {
184             $wsdl .= '
185            
186            
187            
188            
189            
190            
191            
192            
193             ';
194             }
195             $wsdl .= '
196              
197             ';
198              
199             # Generate service
200             $wsdl .= '
201            
202             203              
204             $wsdl = [ $wsdl, '" />
205            
206            
207              
208            
209             ' ];
210              
211             $self->{wsdl}->{$module} = $wsdl;
212              
213             $self;
214             }
215              
216             =head2 __wsdl_gen_complex_types
217              
218             =cut
219              
220             sub __wsdl_gen_complex_types {
221             my @values = @_;
222             my $wsdl = '';
223              
224             while (scalar @values) {
225             my $values = pop(@values);
226            
227             if (ref($values) eq 'ARRAY' and scalar @$values == 2) {
228             my $key = $values->[0];
229             $values = $values->[1];
230            
231             if (blessed $values) {
232             $wsdl .= '
233             ';
234             if ($values->isa('Lim::RPC::Value::Collection')) {
235             $values = $values->children;
236             }
237             }
238             else {
239             $wsdl .= '
240             ';
241             }
242             }
243            
244             if (ref($values) eq 'HASH') {
245             my $nested = 0;
246            
247             foreach my $key (keys %$values) {
248             if (blessed $values->{$key}) {
249             if ($values->{$key}->isa('Lim::RPC::Value::Collection')) {
250             unless ($nested) {
251             $nested = 1;
252             push(@values, 1);
253             }
254             push(@values, [$key, $values->{$key}->children]);
255             }
256             else {
257             $wsdl .= '
258             ';
259             }
260             }
261             elsif (ref($values->{$key}) eq 'HASH') {
262             unless ($nested) {
263             $nested = 1;
264             push(@values, 1);
265             }
266             push(@values, [$key, $values->{$key}]);
267             }
268             }
269            
270             if ($nested) {
271             next;
272             }
273             }
274            
275             unless (scalar @values) {
276             last;
277             }
278            
279             $wsdl .= '
280             ';
281             }
282            
283             $wsdl;
284             }
285              
286             =head2 handle
287              
288             =cut
289              
290             sub handle {
291             my ($self, $cb, $request, $transport) = @_;
292            
293             unless (blessed($request) and $request->isa('HTTP::Request')) {
294             return;
295             }
296              
297             if ($request->header('SOAPAction') and $request->uri =~ /^\/([a-zA-Z]+)\s*$/o) {
298             my ($module) = ($1);
299             my $response = HTTP::Response->new;
300             $response->request($request);
301             $response->protocol($request->protocol);
302            
303             $module = lc($module);
304             my $server = $self->server;
305             if (defined $server and $server->have_module($module) and exists $self->{soap}->{$server->module_class($module)}) {
306             my ($action, $method_uri, $method_name);
307             my $real_self = $self;
308             my $soap = $self->{soap}->{$server->module_class($module)};
309             weaken($self);
310             weaken($soap);
311              
312             Lim::RPC_DEBUG and $self->{logger}->debug('SOAP dispatch to module ', $server->module_class($module), ' obj ', $server->module_obj($module), ' proto obj ', $server->module_obj_by_protocol($module, $self->name));
313              
314             $soap->on_dispatch(sub {
315             my ($request) = @_;
316            
317             unless (defined $self and defined $soap) {
318             return;
319             }
320            
321             $request->{__lim_rpc_protocol_soap_cb} = Lim::RPC::Callback->new(
322             cb => sub {
323             my ($data) = @_;
324            
325             unless (defined $self and defined $soap) {
326             return;
327             }
328            
329             if (blessed $data and $data->isa('Lim::Error')) {
330             $soap->make_fault($data->code, $data->message);
331             }
332             else {
333             my $result;
334            
335             if (defined $data) {
336             $result = $soap->serializer
337             ->prefix('s')
338             ->uri($method_uri)
339             ->envelope(response => $method_name . 'Response', SOAP::Data->value(__soap_result('base', $data)));
340             }
341             else {
342             $result = $soap->serializer
343             ->prefix('s')
344             ->uri($method_uri)
345             ->envelope(response => $method_name . 'Response');
346             $result =~ s/ xsi:nil="true"//go;
347             }
348            
349             $soap->make_response($SOAP::Constants::HTTP_ON_SUCCESS_CODE, $result);
350             }
351            
352             $response = $soap->response;
353             $response->header(
354             'Cache-Control' => 'no-cache',
355             'Pragma' => 'no-cache'
356             );
357            
358             $cb->cb->($response);
359             return;
360             },
361             reset_timeout => sub {
362             $cb->reset_timeout;
363             });
364            
365             return;
366             });
367            
368             $soap->on_action(sub {
369             ($action, $method_uri, $method_name) = @_;
370             });
371              
372             eval {
373             $soap->request($request);
374             $soap->handle;
375             };
376             if ($@) {
377             Lim::WARN and $self->{logger}->warn('SOAP action failed: ', $@);
378             $response->code(HTTP_INTERNAL_SERVER_ERROR);
379             }
380             else {
381             if ($soap->response) {
382             $cb->cb->($soap->response);
383             }
384             return 1;
385             }
386             }
387             else {
388             return;
389             }
390              
391             $cb->cb->($response);
392             return 1;
393             }
394             elsif ($request->uri =~ /^\/([a-zA-Z]+)\.wsdl/o) {
395             my ($module) = ($1);
396             my $response = HTTP::Response->new;
397             $response->request($request);
398             $response->protocol($request->protocol);
399            
400             $module = lc($module);
401             my $server = $self->server;
402             if (defined $server and $server->have_module($module) and exists $self->{wsdl}->{$server->module_class($module)}) {
403             my $wsdl = $self->{wsdl}->{$server->module_class($module)};
404             my $uri = $transport->uri->clone;
405             $uri->path($module);
406            
407             $response->content($wsdl->[0].
408             $uri->as_string.
409             $wsdl->[1]);
410             $response->header(
411             'Content-Type' => 'text/xml; charset=utf-8',
412             'Cache-Control' => 'no-cache',
413             'Pragma' => 'no-cache'
414             );
415             $response->code(HTTP_OK);
416             }
417             else {
418             return;
419             }
420              
421             $cb->cb->($response);
422             return 1;
423             }
424             return;
425             }
426              
427             =head2 __soap_result
428              
429             =cut
430              
431             sub __soap_result {
432             my @a;
433            
434             foreach my $k (keys %{$_[1]}) {
435             if (ref($_[1]->{$k}) eq 'ARRAY') {
436             foreach my $v (@{$_[1]->{$k}}) {
437             if (ref($v) eq 'HASH') {
438             push(@a,
439             SOAP::Data->new->name($k)
440             ->value(Lim::RPC::__soap_result($_[0].'.'.$k, $v))
441             );
442             }
443             else {
444             push(@a,
445             SOAP::Data->new->name($k)
446             ->value($v)
447             );
448             }
449             }
450             }
451             elsif (ref($_[1]->{$k}) eq 'HASH') {
452             push(@a,
453             SOAP::Data->new->name($k)
454             ->value(Lim::RPC::__soap_result($_[0].'.'.$k, $_[1]->{$k}))
455             );
456             }
457             else {
458             push(@a,
459             SOAP::Data->new->name($k)
460             ->value($_[1]->{$k})
461             );
462             }
463             }
464              
465             if ($_[0] eq 'base') {
466             return @a;
467             }
468             else {
469             return \@a;
470             }
471             }
472              
473             =head2 precall
474              
475             =cut
476              
477             sub precall {
478             my ($self, $call, $object) = @_;
479             my $som = pop(@_);
480            
481             unless (ref($call) eq '' and blessed($object) and blessed($som) and $som->isa('SOAP::SOM')) {
482             confess __PACKAGE__, ': Invalid SOAP call';
483             }
484              
485             unless (exists $som->{__lim_rpc_protocol_soap_cb} and blessed($som->{__lim_rpc_protocol_soap_cb}) and $som->{__lim_rpc_protocol_soap_cb}->isa('Lim::RPC::Callback')) {
486             confess __PACKAGE__, ': SOAP::SOM does not contain lim rpc callback or invalid';
487             }
488             my $cb = delete $som->{__lim_rpc_protocol_soap_cb};
489             my $valueof = $som->valueof('//'.$call.'/');
490            
491             if ($valueof) {
492             unless (ref($valueof) eq 'HASH') {
493             confess __PACKAGE__, ': Invalid data in SOAP call';
494             }
495             }
496             else {
497             undef($valueof);
498             }
499              
500             return ($object, $cb, $valueof);
501             }
502              
503             =head1 AUTHOR
504              
505             Jerry Lundström, C<< >>
506              
507             =head1 BUGS
508              
509             Please report any bugs or feature requests to L.
510              
511             =head1 SUPPORT
512              
513             You can find documentation for this module with the perldoc command.
514              
515             perldoc Lim
516              
517             You can also look for information at:
518              
519             =over 4
520              
521             =item * Lim issue tracker (report bugs here)
522              
523             L
524              
525             =back
526              
527             =head1 ACKNOWLEDGEMENTS
528              
529             =head1 LICENSE AND COPYRIGHT
530              
531             Copyright 2012-2013 Jerry Lundström.
532              
533             This program is free software; you can redistribute it and/or modify it
534             under the terms of either: the GNU General Public License as published
535             by the Free Software Foundation; or the Artistic License.
536              
537             See http://dev.perl.org/licenses/ for more information.
538              
539              
540             =cut
541              
542             1; # End of Lim::RPC::Protocol::SOAP