File Coverage

blib/lib/JOAP/Proxy.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # JOAP::Proxy -- Base Class for Things JOAP Clients Use
2             #
3             # Copyright (c) 2003, Evan Prodromou
4             #
5             # This library is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU Lesser General Public
7             # License as published by the Free Software Foundation; either
8             # version 2.1 of the License, or (at your option) any later version.
9             #
10             # This library is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13             # Lesser General Public License for more details.
14             #
15             # You should have received a copy of the GNU Lesser General Public
16             # License along with this library; if not, write to the Free Software
17             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18              
19             # tag: JOAP client object base class
20              
21             package JOAP::Proxy;
22 2     2   3992 use base qw/Exporter Class::Data::Inheritable/;
  2         4  
  2         1980  
23              
24 2     2   716 use 5.008;
  2         7  
  2         66  
25 2     2   10 use strict;
  2         2  
  2         48  
26 2     2   10 use warnings;
  2         3  
  2         70  
27 2     2   1936 use Net::Jabber qw/Client/;
  0            
  0            
28             use JOAP;
29             use Error qw(:try);
30             use Symbol;
31             use JOAP::Proxy::Error;
32              
33             our %EXPORT_TAGS = ( 'all' => [ qw// ] );
34             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
35             our @EXPORT = qw//;
36              
37             our $VERSION = $JOAP::VERSION;
38             our $AUTOLOAD;
39              
40             JOAP::Proxy->mk_classdata('Connection');
41              
42             sub get {
43              
44             my $proto = shift;
45             my $pkg = ref($proto) || $proto;
46             my $address = shift;
47             my $self = bless({_address => $address}, $pkg);
48              
49             $self->_read();
50              
51             return $self;
52             }
53              
54             sub refresh {
55              
56             my $self = shift;
57              
58             # XXX: anything else?
59              
60             return $self->_read;
61             }
62              
63             sub save {
64              
65             my $self = shift;
66              
67             # XXX: anything else?
68              
69             return $self->_edit;
70             }
71              
72             sub address {
73             my $self = shift;
74             return $self->{_address};
75             }
76              
77             sub timestamp {
78             my $self = shift;
79             return $self->{_timestamp};
80             }
81              
82             sub _set_timestamp {
83             my $self = shift;
84             return $self->{_timestamp} = shift;
85             }
86              
87             sub description {
88             my $self = shift;
89             return $self->{_description};
90             }
91              
92             sub _set_description {
93             my $self = shift;
94             return $self->{_description} = shift;
95             }
96              
97             sub attributes {
98             my $self = shift;
99             return (@_) ? $self->{_attributes} = shift : $self->{_attributes};
100             }
101              
102             sub methods {
103             my $self = shift;
104             return (@_) ? $self->{_methods} = shift : $self->{_methods};
105             }
106              
107             sub _attribute_descriptor {
108             my $self = shift;
109             my $name = shift;
110             return $self->attributes->{$name};
111             }
112              
113             sub _method_descriptor {
114             my $self = shift;
115             my $name = shift;
116             return $self->methods->{$name};
117             }
118              
119             sub _described {
120             my $self = shift;
121             return $self->timestamp;
122             }
123              
124             sub _read {
125              
126             my $self = shift;
127             my $con = $self->Connection;
128              
129             if (!$con) {
130             throw JOAP::Proxy::Error::Local("No JOAP proxy connection set.");
131             }
132              
133             if (!$self->_described) {
134             $self->_describe;
135             }
136              
137             my $iq = new Net::Jabber::IQ;
138              
139             $iq->SetTo($self->address);
140             $iq->SetType('get');
141             $iq->NewQuery($JOAP::NS, 'read');
142              
143             # XXX: configure to allow reading just some attributes
144              
145             my $resp = $con->SendAndReceiveWithID($iq);
146              
147             if ($resp->GetType eq 'error') {
148             my $code = $resp->GetErrorCode;
149             my $text = $resp->GetError;
150             throw JOAP::Proxy::Error::Remote($text, $code);
151             }
152              
153             my $read = $resp->GetQuery;
154              
155             my @attrs = $read->GetAttribute;
156              
157             foreach my $attr (@attrs) {
158             my $name = $attr->GetName;
159             # XXX: check returned attributes for type
160             my $value = JOAP->decode($attr->GetValue);
161             $self->_set($name, $value);
162             }
163              
164             # FIXME: what should we return?
165              
166             return $resp;
167             }
168              
169             sub _set {
170              
171             my $self = shift;
172             my $name = shift;
173             my $value = shift;
174              
175             $self->{$name} = $value;
176             }
177              
178             sub _edit {
179              
180             my $self = shift;
181             my $con = $self->Connection;
182              
183             if (!$con) {
184             throw JOAP::Proxy::Error::Local("No JOAP proxy connection set.");
185             }
186              
187             if (!$self->_described) {
188             $self->_describe;
189             }
190              
191             my $iq = new Net::Jabber::IQ;
192              
193             $iq->SetTo($self->address);
194             $iq->SetType('set');
195             my $edit = $iq->NewQuery($JOAP::NS, 'edit');
196              
197             my $attrs = $self->_default_edit_attrs();
198              
199             while (my($name, $descriptor) = each %$attrs) {
200             no strict 'refs';
201             my $loc = $self->$name;
202             use strict 'refs';
203             my $tval = JOAP->encode($descriptor->{type}, $loc);
204             my $val = $edit->AddAttribute(name => $name)->AddValue;
205             JOAP->copy_value($tval, $val);
206             }
207              
208             my $resp = $con->SendAndReceiveWithID($iq);
209              
210             if ($resp->GetType eq 'error') {
211             throw JOAP::Proxy::Error::Remote($resp->GetError, $resp->GetErrorCode);
212             }
213              
214             return $resp;
215             }
216              
217             sub _default_edit_attrs {
218              
219             my $self = shift;
220              
221             my $attrs = $self->attributes;
222              
223             # find names of writable attributes
224              
225             my @writable = grep { $attrs->{$_}->{writable} } keys %$attrs;
226              
227             # make that into a hash
228              
229             my %write = map {($_, $attrs->{$_})} @writable;
230              
231             # return a reference to that hash
232              
233             return \%write;
234             }
235              
236             sub _describe {
237              
238             my $self = shift;
239             my $con = $self->Connection;
240              
241             if (!$con) {
242             throw JOAP::Proxy::Error::Local("No JOAP proxy connection set.");
243             }
244              
245             my $iq = new Net::Jabber::IQ;
246              
247             $iq->SetTo($self->address);
248             $iq->SetType('get');
249             $iq->NewQuery($JOAP::NS, 'describe');
250              
251             my $resp = $con->SendAndReceiveWithID($iq);
252              
253             if ($resp->GetType eq 'error') {
254             throw JOAP::Proxy::Error::Remote($resp->GetError, $resp->GetErrorCode);
255             }
256              
257             my $desc = $resp->GetQuery;
258              
259             # FIXME: handle multiple descriptions
260              
261             if ($desc->DefinedDesc) {
262             $self->_set_description($desc->GetDesc);
263             }
264              
265             my $attrs = {};
266              
267             my @attrdescs = $desc->GetAttributeDescription;
268              
269             foreach my $attrdesc (@attrdescs) {
270              
271             my $name = $attrdesc->GetName;
272             my $type = $attrdesc->GetType;
273             my $required = $attrdesc->GetRequired || 0;
274             my $writable = $attrdesc->GetWritable || 0;
275             my $allocation = $attrdesc->GetAllocation || 'instance';
276             my $desc = $attrdesc->GetDesc || '';
277              
278             $attrs->{$attrdesc->GetName} = {name => $name,
279             type => $type,
280             required => $required,
281             writable => $writable,
282             allocation => $allocation,
283             desc => $desc};
284             }
285              
286             $self->attributes($attrs);
287              
288             my $meths = {};
289              
290             my @methdescs = $desc->GetMethodDescription;
291              
292             foreach my $methdesc (@methdescs) {
293             $meths->{$methdesc->GetName} = {name => $methdesc->GetName,
294             returnType => $methdesc->GetReturnType,
295             allocation => $methdesc->GetAllocation,
296             desc => $methdesc->GetDesc};
297              
298             my $params = [];
299             my @params = $methdesc->GetParams->GetParams;
300              
301             foreach my $param (@params) {
302             push @$params, {name => $param->GetName,
303             type => $param->GetType,
304             desc => $param->GetDesc};
305             }
306              
307             $meths->{$methdesc->GetName}->{params} = $params;
308             }
309              
310             $self->methods($meths);
311              
312             # save the timestamp
313              
314             $self->_set_timestamp($desc->GetTimestamp);
315              
316             return $resp;
317             }
318              
319             # This allows us to say $self->can('autoloadedmethod'). AUTOLOAD (below)
320             # uses this method to create methods if necessary.
321              
322             sub can {
323              
324             my($self) = shift;
325             my($name) = shift;
326             my($func) = $self->SUPER::can($name); # See if it's findable by standard lookup.
327              
328             if (!$func) { # if not, see if it's something we should make ourselves.
329             if (my $methdesc = $self->_method_descriptor($name)) {
330             $func = $self->_proxy_method($methdesc);
331             } elsif (my $attrdesc = $self->_attribute_descriptor($name)) {
332             $func = $self->_proxy_accessor($attrdesc);
333             }
334             }
335              
336             return $func;
337             }
338              
339             sub _proxy_method {
340              
341             my $self = shift;
342             my $methdesc = shift;
343              
344             my @param_types = map { $_->{type} } @{$methdesc->{params}};
345             my $param_cnt = scalar(@param_types);
346              
347             my $name = $methdesc->{name};
348              
349             return sub {
350              
351             my $self = shift;
352              
353             my $con = $self->Connection || throw JOAP::Proxy::Error::Local("Can't call remote method if not connected.");
354              
355             my @args = @_;
356              
357             # XXX: allow named parameters if scalar(@args) == $param_cnt * 2
358              
359             if (scalar(@args) != $param_cnt) {
360             throw JOAP::Proxy::Error::Local("Wrong number of parameters (need $param_cnt) for method '$name'.");
361             }
362              
363             my $iq = new Net::Jabber::IQ;
364             $iq->SetIQ(to => $self->address, type => 'set');
365              
366             my $mc = $iq->NewQuery('jabber:iq:rpc')->AddMethodCall;
367              
368             $mc->SetMethodName($name);
369              
370             my $params = $mc->AddParams;
371              
372             my $i;
373              
374             for ($i = 0; $i < $param_cnt; $i++) {
375             my $pv = $params->AddParam->AddValue;
376             my $tv = JOAP->encode($param_types[$i], $args[$i]);
377             JOAP->copy_value($tv, $pv);
378             }
379              
380             my $resp = $con->SendAndReceiveWithID($iq);
381              
382             if ($resp->GetType eq 'error') {
383             throw JOAP::Proxy::Error::Remote($resp->GetError, $resp->GetErrorCode);
384             }
385              
386             my $mr = $resp->GetQuery->GetMethodResponse;
387              
388             if ($mr->DefinedFault) {
389              
390             my $struct = $mr->GetFault->GetValue->GetStruct;
391             my ($code, $text);
392              
393             foreach my $member ($struct->GetMembers()) {
394             if ($member->GetName eq 'faultCode') {
395             $code = JOAP->decode($member->GetValue);
396             } elsif ($member->GetName eq 'faultString'){
397             $text = JOAP->decode($member->GetValue);
398             }
399             }
400              
401             throw JOAP::Proxy::Error::Fault($text, $code);
402              
403             } else {
404             # FIXME: check return type
405             my @results = map { JOAP->decode($_->GetValue) } $mr->GetParams->GetParams;
406             return @results;
407             }
408             };
409             }
410              
411             sub _proxy_accessor {
412              
413             my $self = shift;
414             my $descriptor = shift;
415              
416             my $name = $descriptor->{name};
417             my $writable = $descriptor->{writable};
418             my $type = $descriptor->{type};
419              
420             my $func = undef;
421              
422             if ($writable) {
423             $func = sub {
424             my $self = shift;
425             return (@_) ? $self->{$name} = JOAP->coerce($type, shift) : $self->{$name};
426             };
427             } else {
428             $func = sub {
429             my $self = shift;
430             if (@_) {
431             throw JOAP::Proxy::Error::Local("Can't modify read-only attribute $name.");
432             }
433             return $self->{$name};
434             };
435             }
436              
437             return $func;
438             }
439              
440             sub AUTOLOAD {
441              
442             my ($self) = $_[0];
443             my ($sub) = $AUTOLOAD;
444              
445             my ($pkg,$name) = ($sub =~ /(.*)::([^:]+)$/);
446             my ($func) = $self->can($name);
447              
448             if ($func) {
449             &$func(@_);
450             } else {
451             throw JOAP::Proxy::Error::Local("No attribute or method '$name'");
452             }
453             }
454              
455             # skip autoload hoohaw for DESTROY
456              
457             sub DESTROY { }
458              
459             1; # of these days, Alice
460              
461             __END__