File Coverage

blib/lib/JOAP/Server/Class.pm
Criterion Covered Total %
statement 3 3 100.0
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 4 4 100.0


line stmt bran cond sub pod time code
1             # JOAP::Server::Class -- Base Class for JOAP Server-Side Classes and Instances
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 server class framework
20              
21             package JOAP::Server::Class;
22 3     3   90331 use base qw/Exporter JOAP::Server::Object/;
  3         9  
  3         14723  
23              
24             use 5.008;
25             use strict;
26             use warnings;
27              
28             use Net::Jabber qw/Component/;
29             use JOAP;
30             use JOAP::Server::Object;
31             use JOAP::Server;
32             use Error;
33              
34             require Exporter;
35              
36             our %EXPORT_TAGS = ( 'all' => [] );
37              
38             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
39              
40             our @EXPORT = ();
41              
42             our $VERSION = $JOAP::VERSION;
43              
44             JOAP::Server::Class->mk_classdata('Instances');
45             JOAP::Server::Class->mk_classdata('Superclasses');
46             JOAP::Server::Class->mk_classdata('Id');
47             JOAP::Server::Class->mk_classdata('IdFormat');
48             JOAP::Server::Class->mk_classdata('Separator');
49              
50             JOAP::Server::Class->Instances({}); # Initially, no instances
51             JOAP::Server::Class->Superclasses([]);
52             JOAP::Server::Class->Separator (',');
53             JOAP::Server::Class->Id([]);
54             JOAP::Server::Class->IdFormat(undef);
55              
56             # return the instance with the given instance ID
57              
58             sub get {
59             my $self = shift;
60              
61             return $self->Instances->{$_[0]};
62             }
63              
64             # handle a JOAP verb
65              
66             sub on_add {
67              
68             my($self) = shift;
69             my($iq) = shift;
70              
71             if (ref $self) { # Can't call add on instances
72             return $self->SUPER::on_add($iq);
73             }
74              
75             my $respiq = $self->reply($iq);
76              
77             if (my($code, $text) = $self->_validate_add($iq)) {
78             $respiq->SetType('error');
79             $respiq->SetErrorCode($code);
80             $respiq->SetError($text);
81             return $respiq;
82             }
83              
84             # this line is too long.
85              
86             my(%args) = map {($_->GetName, JOAP->decode($_->GetValue))} $iq->GetQuery->GetAttribute;
87              
88             my $id = $self->_make_id(%args);
89              
90             # Do we already have one of these?
91              
92             if ($self->get_instance($id)) {
93             $respiq->SetType('error');
94             $respiq->SetErrorCode(406); # not acceptable
95             $respiq->SetError("An instance with this ID already exists.");
96             return $respiq;
97             }
98              
99             my $inst = $self->new(%args);
100              
101             $self->set_instance($inst->_id, $inst);
102              
103             # The address we should return is mostly in the $iq.
104              
105             my($jid) = $iq->GetTo('jid');
106             $jid->SetResource($inst->_id);
107              
108             $respiq->GetQuery->SetNewAddress($jid->GetJID('full'));
109              
110             return $respiq;
111             }
112              
113             # handle a JOAP verb
114              
115             sub on_edit {
116              
117             my $self = shift;
118             my $pkg = ref($self) || $self;
119             my $iq = shift;
120             my $respiq = shift;
121             my $oldid;
122              
123             # Save the old ID if this is an instance.
124              
125             if (ref($self)) {
126             $oldid = $self->_id();
127             }
128              
129             # Do the default editing schtuff
130              
131             $respiq = $self->SUPER::on_edit($iq);
132              
133             # If this is an instance, and the ID has changed, set the newaddress value
134              
135             if (ref($self)) {
136             my $instid = $self->_id;
137              
138             if ($oldid ne $instid) {
139              
140             $self->delete_instance($oldid);
141             $self->set_instance($instid, $self);
142              
143             my($jid) = $iq->GetTo('jid');
144             $jid->SetResource($self->_id());
145             $respiq->GetQuery->SetNewAddress($jid->GetJID('full'));
146             }
147             }
148              
149             return $respiq;
150             }
151              
152             # handle a JOAP verb
153              
154             sub on_delete {
155             my $self = shift;
156             my $pkg = ref($self) || $self;
157             my $iq = shift;
158             my $respiq = $self->reply($iq);
159              
160             if (!ref($self)) { # Can't call delete on a class
161             return $self->SUPER::on_delete($iq);
162             }
163              
164             $pkg->delete_instance($self->_id());
165              
166             # XXX: do we need to allow the instance a cleanup?
167              
168             return $respiq;
169             }
170              
171             # handle a JOAP verb
172              
173             sub on_search {
174              
175             my($self) = shift;
176             my($iq) = shift;
177              
178             if (ref($self)) { # class method
179             return $self->SUPER::on_search($iq);
180             }
181              
182             my($respiq) = $self->reply($iq);
183              
184             if (my($code, $text) = $self->_validate_search($iq)) {
185             $respiq->SetType('error');
186             $respiq->SetErrorCode($code);
187             $respiq->SetError($text);
188             return $respiq;
189             }
190              
191             # FIXME: This doesn't get instances of subclasses.
192             # XXX: This is big, sloppy, dumb, and linear.
193              
194             my %match = map {($_->GetName, JOAP->decode($_->GetValue))} $iq->GetQuery->GetAttribute;
195              
196             # sneakily add
197              
198             my $addr = $iq->GetTo;
199              
200             my $resp = $respiq->GetQuery;
201              
202             $self->_iterate(sub
203             {
204             my $inst = $_;
205              
206             if (!%match || $inst->_match_all(%match)) {
207             # XXX: this assumes that the item is a direct instance
208             my $jid = new Net::Jabber::JID($addr);
209             $jid->SetResource($inst->_id);
210             $resp->SetItem($jid->GetJID('full'));
211             }
212             });
213              
214             return $respiq;
215             }
216              
217             # handle a JOAP verb; we need to add our superclasses.
218              
219             sub on_describe {
220              
221             my($self) = shift;
222             my($iq) = shift;
223             my($respiq) = $self->SUPER::on_describe($iq);
224              
225             if ($respiq->GetType() ne 'error') { # If that worked out OK...
226             my $qry = $respiq->GetQuery;
227             foreach my $class (@{$self->Superclasses}) {
228             $qry->AddSuperclass($self->make_address(classname => $class));
229             }
230             $qry->SetTimestamp($self->timestamp);
231             }
232              
233             return $respiq;
234             }
235              
236             # validators
237              
238             # these return a list of ($code, $text) if there's an error, or an
239             # empty list for success
240              
241             # validate an incoming request
242              
243             sub _validate_edit {
244              
245             my $self = shift;
246             my $reqiq = shift;
247              
248             if (my($code, $text) = $self->SUPER::_validate_edit($reqiq)) {
249             return ($code, $text);
250             }
251              
252             # You can set class variables through instances, but you can't set
253             # instance variables through classes.
254              
255             if (!ref($self)) {
256             my(@names) = map { $_->GetName } $reqiq->GetQuery->GetAttribute;
257              
258             my(@inst) = grep { $self->_attribute_allocation($_) ne 'class' } @names;
259              
260             if (@inst) {
261             return (406, join("\n", map "Can't edit instance variable $_ in class", @inst));
262             }
263             }
264              
265             # empty list indicates success
266              
267             return ();
268             }
269              
270             # validate an incoming method
271              
272             sub _validate_method {
273              
274             my $self = shift;
275             my $reqiq = shift;
276              
277             if (my($code, $text) = $self->SUPER::_validate_method($reqiq)) {
278             return ($code, $text);
279             }
280              
281             # You can call class methods on instances, but not vice versa
282              
283             if (!ref($self)) {
284              
285             my $method = $reqiq->GetQuery->GetMethodCall->GetMethodName;
286              
287             if ($self->_method_allocation($method) ne 'class') {
288             return (406, join("\n", map "Can't call instance method $_ on class", $method));
289             }
290             }
291              
292             return ();
293             }
294              
295             # validate an incoming request
296              
297             sub _validate_read {
298              
299             my $self = shift;
300             my $reqiq = shift;
301              
302             if (my($code, $text) = $self->SUPER::_validate_read($reqiq)) {
303             return ($code, $text);
304             }
305              
306             # Check to see if they're trying to read an instance attribute from a class.
307              
308             if (!ref($self)) {
309             my(@names) = $reqiq->GetQuery->GetName;
310              
311             if (@names) {
312             my(@inst) = grep { $self->_attribute_allocation($_) ne 'class' } @names;
313              
314             if (@inst) {
315             return (406, join("\n", map "Can't read instance variable $_ in class", @inst));
316             }
317             }
318             }
319              
320             return ();
321             }
322              
323             # validate an incoming request
324              
325             sub _validate_add {
326             my $self = shift;
327             my $reqiq = shift;
328              
329             my @attrs = $self->_attribute_names;
330              
331             my @toset = $reqiq->GetQuery->GetAttribute;
332              
333             my @names = map {$_->GetName} @toset;
334              
335             # XXX: Move these checks to their own functions
336              
337             # Are there any attrs to set that aren't in our object?
338              
339             my @unknown = grep {my($a) = $_; ! grep {/$a/} @attrs} @names;
340              
341             if (@unknown) {
342             return (406, join("\n", map {"No such attribute '$_'."} @unknown));
343             }
344              
345             # Check for stuff that isn't writable.
346              
347             my @notallowed = grep { !$self->_attribute_writable($_) } @names;
348              
349             if (@notallowed) {
350             return (406, join("\n", map {"Cannot edit attribute '$_'."} @notallowed));
351             }
352              
353             # Are all required, writable attributes present?
354              
355             my @reqwrite = grep {$self->_attribute_required($_) && $self->_attribute_writable($_)} @attrs;
356              
357             my @unmatched = grep {my($a) = $_; ! grep {/$a/} @names} @reqwrite;
358              
359             if (@unmatched) {
360             return (406, join("\n", map {"Required attribute '$_' not set."} @unknown));
361             }
362              
363             # Are all attribute values acceptable?
364              
365             my @notok = grep {!$self->_attribute_ok($_->GetName, $_->GetValue)} @toset;
366              
367             if (@notok) {
368             return (406, join("\n", map {"Value for attribute '" . $_->GetName . "' not acceptable."} @notok));
369             }
370              
371             # empty list means "no probs"
372              
373             return ();
374             }
375              
376             # validate an incoming request
377              
378             sub _validate_search {
379              
380             my $self = shift;
381             my $reqiq = shift;
382              
383             my @attrs = $self->_attribute_names;
384              
385             my @match = $reqiq->GetQuery->GetAttribute;
386              
387             my @names = map {$_->GetName} @match;
388              
389             # Are there any attrs to set that aren't in our object?
390              
391             my @unknown = grep {my($a) = $_; ! grep {/$a/} @attrs} @names;
392              
393             if (@unknown) {
394             return (406, join("\n", map {"No such attribute '$_'."} @unknown));
395             }
396              
397             # Are there any class attributes in there?
398              
399             my @classattrs = grep {$self->_attribute_allocation($_) eq 'class'} @names;
400              
401             if (@classattrs) {
402             return (406, join("\n", map {"Can't search on class attribute '$_'."} @classattrs));
403             }
404              
405             # Are all attribute values acceptable?
406              
407             my @notok = grep {!$self->_attribute_match_ok($_->GetName, $_->GetValue)} @match;
408              
409             if (@notok) {
410             return (406, join("\n", map {"Value for attribute '" . $_->GetName . "' not acceptable."} @notok));
411             }
412              
413             # empty list means "no probs"
414              
415             return ();
416              
417             }
418              
419             # right now, just check the type
420              
421             sub _attribute_match_ok {
422              
423             my $self = shift;
424             my $name = shift;
425             my $value = shift;
426             my $type = $self->_attribute_type($name);
427              
428             return $self->_type_match($type, $value);
429             }
430              
431             # matching semantics
432              
433             sub _match_all {
434              
435             my $self = shift;
436             my %match = @_;
437              
438             while (my($attr, $value) = each %match) {
439             if (!$self->_match($attr, $value)) {
440             return 0;
441             }
442             }
443              
444             return 1;
445             }
446              
447             # match a single attribute and value
448              
449             sub _match {
450              
451             my $self = shift;
452             my $attr = shift;
453             my $match = shift;
454              
455             my $value = $self->_attribute_get($attr);
456             my $type = $self->_attribute_type($attr);
457              
458             if ($type eq 'i4' || $type eq 'int' || $type eq 'double') {
459             return $value == $match;
460             }
461             elsif ($type eq 'boolean') {
462             return (($value && $match) || (!$value && !$match));
463             }
464             elsif ($type eq 'string') {
465             return (index($value, $match) != -1);
466             }
467             elsif ($type eq 'dateTime.iso8601') {
468             # XXX: it'd be nicer to use integer-compare here; maybe use dts at
469             # ints internally?
470             return $value eq $match;
471             }
472             elsif ($type eq 'struct') {
473             # FIXME: make this work
474             return 0;
475             }
476             elsif ($type eq 'array') {
477             # FIXME: make this work
478             return 0;
479             }
480             }
481              
482             # store an instance with the given instance ID
483              
484             sub set_instance {
485              
486             my($self) = shift;
487             my($pkg) = ref($self) || $self;
488             my($instid) = shift;
489             my $inst = shift;
490              
491             $pkg->Instances->{$instid} = $inst;
492             }
493              
494             # get an instance with the given instance ID
495              
496             sub get_instance {
497              
498             my($self) = shift;
499             my($pkg) = ref($self) || $self;
500             my($instid) = shift;
501              
502             return $pkg->Instances->{$instid};
503             }
504              
505             # delete an instance with the given instance ID
506              
507             sub delete_instance {
508              
509             my($self) = shift;
510             my($pkg) = ref($self) || $self;
511             my($instid) = shift;
512              
513             return delete $pkg->Instances->{$instid};
514             }
515              
516             # Return the string value representing the instance ID
517              
518             sub _id {
519              
520             my $self = shift;
521             my $pkg = ref($self);
522              
523             my @ids = map {$self->_attribute_get($_)} @{$self->Id};
524              
525             return ($self->IdFormat) ?
526             sprintf($self->IdFormat, @ids) :
527             join($self->Separator, @ids);
528             }
529              
530             # Return the string value representing the instance ID given a set of
531             # attributes
532              
533             sub _make_id {
534             my $self = shift;
535             my %attrs = @_;
536              
537             my @ids = map {$attrs{$_}} @{$self->Id};
538              
539             return ($self->IdFormat) ?
540             sprintf($self->IdFormat, @ids) :
541             join($self->Separator, @ids);
542             }
543              
544             # Iterate some code over all instances of this class
545              
546             sub _iterate($\&) {
547              
548             my($self) = shift;
549             my($block) = shift;
550              
551             while (my($id, $inst) = each %{$self->Instances}) {
552             $_ = $inst;
553             eval &$block;
554             }
555             }
556              
557             # used by _read; defines which attributes should be returned for a
558             # verb with no arguments
559              
560             sub _attribute_read_names {
561              
562             my $self = shift;
563             my @names = $self->_attribute_names;
564              
565             if (ref($self)) {
566             return grep { $self->_attribute_allocation($_) eq 'instance' } @names;
567             }
568             else {
569             return grep { $self->_attribute_allocation($_) eq 'class' } @names;
570             }
571             }
572              
573             1;
574             __END__