File Coverage

blib/lib/CAM/SOAPClient.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package CAM::SOAPClient;
2              
3             require 5.005_62;
4 2     2   88278 use strict;
  2         4  
  2         77  
5 2     2   9 use warnings;
  2         6  
  2         65  
6 2     2   1190 use SOAP::Lite;
  0            
  0            
7              
8             our $VERSION = '1.17';
9              
10             =for stopwords Lapworth subclassable wsdl
11              
12             =head1 NAME
13              
14             CAM::SOAPClient - SOAP interaction tools
15              
16             =head1 LICENSE
17              
18             Copyright 2006 Clotho Advanced Media, Inc.,
19              
20             This library is free software; you can redistribute it and/or modify it
21             under the same terms as Perl itself.
22              
23             =head1 SYNOPSIS
24              
25             use CAM::SOAPClient;
26             my $client = CAM::SOAPClient->new(wsdl => 'http://www.clotho.com/staff.wsdl');
27             my ($fname, $lname) = $client->call('fetchEmployee', '[firstName,lastName]',
28             ssn => '000-00-0000');
29             my $record = $client->call('fetchEmployee', undef, ssn => '000-00-0000');
30             my @addresses = $client->call('allEmployees', '@email');
31            
32             my $firstbudget = $client->call('listClientProjects',
33             '/client/projects/project/budget');
34            
35             if ($client->hadFault()) {
36             die 'SOAP Fault: ' . $client->getLastFaultString();
37             }
38              
39             =head1 DESCRIPTION
40              
41             This module offers some basic tools to simplify the creation of SOAP
42             client implementations. It is intended to be subclassable, but works
43             fine as-is too.
44              
45             The purpose for this module is to abstract the complexity of
46             SOAP::Lite. That module makes easy things really easy and hard things
47             possible, but quite obscure. The problem is that the easy things are
48             often too basic. For example, calling remote methods with positional
49             arguments is easy, but with named arguments is much harder. Calling
50             methods on a SOAP::Lite server is easy, but an Apache Axis server is
51             much harder. This module attempts to make typical SOAP and WSDL activities
52             easier by hiding some of the weirdness of SOAP::Lite.
53              
54             The main method is call(), via which you can specify what remote
55             method to invoke, what return values you want, and the named arguments
56             you want to pass. See below for more detail.
57              
58             This package has been tested against servers running SOAP::Lite,
59             Apache Axis, and PEAR SOAP.
60              
61             =head1 SEE ALSO
62              
63             L is another module with very similar goals to
64             this one. Leo Lapworth, the author of that module, and I have briefly
65             discussed merging the work into a single package, but have not made
66             much progress. If any users are interested in such a merger, let us know.
67              
68             =head1 METHODS
69              
70             =over
71              
72             =item $pkg->new([opts], $uri)
73              
74             =item $pkg->new([opts], $uri, $proxy)
75              
76             =item $pkg->new([opts], $uri, $proxy, $username, $password)
77              
78             =item $pkg->new([opts], wsdl => $url)
79              
80             =item $pkg->new([opts], wsdl => $url, $username, $password)
81              
82             Create a connection instance. The C<$proxy> is not required here, but
83             if not specified it must be set later via C. Optionally
84             (and recommended) you can specify a WSDL C<$url> instead of a C<$uri> and
85             C<$proxy>.
86              
87             If a C<$username> is specified, then the C<$username> and C<$password>
88             are simply passed to C.
89              
90             The options are as follows:
91              
92             =over
93              
94             =item timeout => $seconds
95              
96             This defaults to 6 hours.
97              
98             =back
99              
100             =cut
101              
102             sub new
103             {
104             my $pkg = shift;
105             my %cfg = (
106             timeout => 6*60*60, # 6 hours
107             );
108             while (@_ > 0)
109             {
110             if ($_[0] eq 'timeout')
111             {
112             my $key = shift;
113             my $val = shift;
114             $cfg{$key} = $val;
115             }
116             else
117             {
118             last;
119             }
120             }
121              
122             my $uri = shift;
123             my $proxy = shift;
124             my $user = shift;
125             my $pass = shift;
126              
127             return if (!$uri);
128              
129             my $soap = SOAP::Lite -> on_fault( sub {} );
130             my $self = bless {
131             %cfg,
132             services => {},
133             soap => $soap,
134             auth => {},
135             global_proxy => undef,
136             global_uri => undef,
137             proxies => {},
138             uris => {},
139             }, $pkg;
140              
141             if ($uri eq 'wsdl')
142             {
143             $self->setWSDL($proxy);
144             }
145             else
146             {
147             $self->setURI($uri);
148             if ($proxy)
149             {
150             $self->setProxy($proxy);
151             }
152             }
153              
154             if ($user)
155             {
156             $self->setUserPass($user, $pass);
157             }
158             return $self;
159             }
160              
161             =item $self->setWSDL($url)
162              
163             Loads a Web Service Description Language file describing the SOAP service.
164              
165             =cut
166              
167             sub setWSDL
168             {
169             my $self = shift;
170             my $url = shift;
171            
172             # The SOAP::Schema API changed as of SOAP::Lite v0.65-beta2
173             my $schema = SOAP::Schema->can('schema_url') ?
174             SOAP::Schema->schema_url($url) :
175             SOAP::Schema->schema($url);
176             my $services = $schema->parse()->services();
177             #use Data::Dumper; print STDERR Dumper($services);
178              
179             foreach my $class (values %{$services})
180             {
181             foreach my $method (keys %{$class})
182             {
183             my $endpoint = $class->{$method}->{endpoint};
184             # 'uri' was used thru SOAP::Lite v0.60, 'namespace' is used in v0.65+
185             my $namespace = $class->{$method}->{uri} ? $class->{$method}->{uri}->value() : $class->{$method}->{namespace};
186             $self->{proxies}->{$method} = $endpoint ? $endpoint->value() : undef;
187             $self->{uris}->{$method} = $namespace;
188             }
189             }
190              
191             return $self;
192             }
193              
194             =item $self->setURI($uri)
195              
196             Specifies the URI for the SOAP server. This is not needed if you are
197             using WSDL.
198              
199             =cut
200              
201             sub setURI
202             {
203             my $self = shift;
204             my $uri = shift;
205              
206             $self->{global_uri} = $uri;
207             return $self;
208             }
209              
210             =item $self->setProxy($proxy)
211              
212             Specifies the URL for the SOAP server. This is not needed if you are
213             using WSDL.
214              
215             =cut
216              
217             sub setProxy
218             {
219             my $self = shift;
220             my $proxy = shift;
221              
222             $self->{global_proxy} = $proxy;
223             return $self;
224             }
225              
226             =item $self->setUserPass($username, $password)
227              
228             Specifies the C<$username> and C<$password> to use on the SOAP server.
229             These values are stored until used via loginParams(). Most
230             applications won't use this method.
231              
232             =cut
233              
234             sub setUserPass
235             {
236             my $self = shift;
237             my $username = shift;
238             my $password = shift;
239              
240             $self->{auth}->{username} = $username;
241             $self->{auth}->{password} = $password;
242             return $self;
243             }
244              
245             =item $self->getLastSOM()
246              
247             Returns the SOAP::SOM object for the last query.
248              
249             =cut
250              
251             sub getLastSOM
252             {
253             my $self = shift;
254              
255             return $self->{last_som};
256             }
257              
258             =item $self->hadFault()
259              
260             Returns a boolean indicating whether the last call() resulted in a fault.
261              
262             =cut
263              
264             sub hadFault
265             {
266             my $self = shift;
267              
268             my $som = $self->getLastSOM();
269             return $som && (ref $som) && $som->fault();
270             }
271              
272             =item $self->getLastFaultCode()
273              
274             Returns the fault code from the last query, or C<(none)> if the last
275             query did not result in a fault.
276              
277             =cut
278              
279             sub getLastFaultCode
280             {
281             my $self = shift;
282              
283             my $som = $self->getLastSOM();
284             if ($som && (ref $som) && $som->can('faultcode') && $som->fault())
285             {
286             return $som->faultcode();
287             }
288             else
289             {
290             return '(none)';
291             }
292             }
293              
294             =item $self->getLastFaultString()
295              
296             Returns the fault string from the last query, or C<(none)> if the last
297             query did not result in a fault.
298              
299             =cut
300              
301             sub getLastFaultString
302             {
303             my $self = shift;
304              
305             my $som = $self->getLastSOM();
306             if ($som && (ref $som) && $som->can('faultstring') && $som->fault())
307             {
308             return $som->faultstring();
309             }
310             else
311             {
312             return '(none)';
313             }
314             }
315              
316             =item $self->getLastFault()
317              
318             Creates a new SOAP::Fault instance from the last fault data, if any. If
319             there was no fault (as per the hadFault() method) then this returns
320             undef.
321              
322             =cut
323              
324             sub getLastFault
325             {
326             my $self = shift;
327              
328             return if (!$self->hadFault());
329              
330             my $som = $self->getLastSOM();
331             return if (!$som || !(ref $som) || !$som->fault());
332              
333             my $code = $som->can('faultcode') ? $som->faultcode() : 'Unknown';
334             my $string = $som->can('faultstring') ? $som->faultstring() : 'An unknown error has occurred';
335             my $detail = $som->can('faultdetail') ? $som->faultdetail() : undef;
336             if ($detail)
337             {
338             $detail = $detail->{data};
339             }
340              
341             my $fault = SOAP::Fault->new(
342             faultcode => $code,
343             faultstring => $string,
344             ($detail ? (faultdetail => SOAP::Data->name('data' => $detail)) : ()),
345             );
346             return $fault;
347             }
348              
349              
350             =item $self->call($method, undef, $key1 => $value1, $key2 => $value, ...)
351              
352             =item $self->call($method, $xpath, $key1 => $value1, $key2 => $value, ...)
353              
354             =item $self->call($method, $xpath_arrayref, $key1 => $value1, $key2 => $value, ...)
355              
356             Invoke the named SOAP method. The return values are indicated in the
357             second argument, which can be undef, a single scalar or a list of
358             return fields. If this path is undef, then all data are returned as
359             if the SOAP C method was called. Otherwise, the SOAP
360             response is searched for these values. If any of them are missing,
361             call() returns undef. If multiple values are specified, they are all
362             returned in array context, while just the first one is returned in
363             scalar context. This is best explained by examples:
364              
365             'documentID'
366             returns
367             /Envelope/Body//documentID
368              
369             ['documentID', 'data/[2]/type', '//result']
370             returns
371             (/Envelope/Body//documentID,
372             /Envelope/Body//data/[2]/type,
373             /Envelope/Body//*/result)
374             or
375             /Envelope/Body//documentID
376             in scalar context
377              
378             If the path matches multiple fields, just the first is returned.
379             Alternatively, if the path is prefixed by a C<@> character, it is
380             expected that the path will match multiple fields. If there is just
381             one path, the matches are returned as an array (just the first one in
382             scalar context). If there are multiple paths specified, then the
383             matches are returned as an array reference. For example, imagine a
384             query that returns a list of documents with IDs 4,6,7,10,20 for user
385             #12. Here we detail the return values for the following paths:
386              
387             path: 'documents/item/id' or ['documents/item/id']
388             returns
389             array context: (4)
390             scalar context: 4
391            
392             path: '@documents/item/id' or ['@documents/item/id']
393             returns
394             array context: (4,6,7,10,20)
395             scalar context: 4
396            
397             path: ['documents/item/id', 'userID']
398             returns
399             array context: (4, 12)
400             scalar context: 4
401            
402             path: ['@documents/item/id', 'userID']
403             returns
404             array context: ([4,6,7,10,20], 12)
405             scalar context: [4,6,7,10,20]
406            
407             path: ['userID', '@documents/item/id']
408             returns
409             array context: (12, [4,6,7,10,20])
410             scalar context: 12
411              
412             =cut
413              
414             sub call
415             {
416             my $self = shift;
417             my $method = shift;
418             my $paths = shift;
419             my @args = @_;
420              
421             my @rets;
422              
423             if ($paths && !ref $paths)
424             {
425             $paths = [$paths];
426             }
427              
428             my $uri = $self->{uris}->{$method} || $self->{global_uri};
429             my $proxy = $self->{proxies}->{$method} || $self->{global_proxy};
430             if (!$uri || !$proxy)
431             {
432             # Create a minimal SOAP fault from scratch
433             $self->_setFault('Client',
434             "Attempted to call method '$method' which lacks a URI or proxy. Are you sure you called the right method?");
435             return;
436             }
437            
438             my $soap = SOAP::Lite->can('ns') ? $self->{soap}->ns($uri) : $self->{soap}->uri($uri);
439             my $som = $soap
440             ->proxy($proxy,
441             ($self->{timeout} ?
442             (timeout => $self->{timeout}) : ())
443             )
444             ->call($method, $self->request($self->loginParams(), @args));
445              
446             if (!$som || !ref $som)
447             {
448             $self->_setFault('Client', 'Communication failure');
449             return;
450             }
451              
452             $self->{last_som} = $som;
453              
454             if ($som->fault)
455             {
456             return;
457             }
458              
459             if (!defined $paths)
460             {
461             @rets = ($som->match('/Envelope/Body/[1]')->valueof());
462             }
463             else
464             {
465             foreach my $origpath (@{$paths})
466             {
467             my $path = $origpath;
468             my $is_array = ($path =~ s/\A\@//xms);
469            
470             return if (!$som->match("/Envelope/Body/[1]/$path"));
471             my @values = $som->valueof();
472             if ($is_array)
473             {
474             if (@{$paths} == 1)
475             {
476             push @rets, @values;
477             }
478             else
479             {
480             push @rets, [@values];
481             }
482             }
483             else
484             {
485             push @rets, $values[0];
486             }
487             }
488             }
489             return wantarray ? @rets : $rets[0];
490             }
491              
492              
493             sub _setFault
494             {
495             my $self = shift;
496             my $code = shift;
497             my $string = shift;
498              
499             $self->{last_som} = SOAP::Deserializer->deserialize(<<"EOF"
500            
501            
502            
503             $code
504             $string
505            
506            
507            
508             EOF
509             );
510             return $self;
511             }
512              
513             =item $self->loginParams()
514              
515             This is intended to return a hash of all the required parameters shared
516             by all SOAP requests. This version returns the contents of
517             C<%{$soap->{auth}}>. Some subclasses may wish to override this, while
518             others may wish to simply add more to that hash.
519              
520             =cut
521              
522             sub loginParams
523             {
524             my $self = shift;
525             return (%{$self->{auth}});
526             }
527              
528             =item $self->request($key1 => $value1, $key2 => $value2, ...)
529              
530             =item $self->request($soapdata1, $soapdata2, ...)
531              
532             Helper routine which wraps its key-value pair arguments in SOAP::Data
533             objects, if they are not already in that form.
534              
535             =cut
536              
537             sub request
538             {
539             my $pkg_or_self = shift;
540             # other args below
541              
542             my @return;
543             while (@_ > 0)
544             {
545             my $var = shift;
546             if ($var && (ref $var) && (ref $var) eq 'SOAP::Data')
547             {
548             push @return, $var;
549             }
550             else
551             {
552             push @return, SOAP::Data->name($var, shift);
553             }
554             }
555             return @return;
556             }
557              
558             1;
559             __END__