File Coverage

blib/lib/SNMP/Class/ResultSet.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             package SNMP::Class::ResultSet;
2              
3             =head1 SNMP::Class::ResultSet
4              
5             SNMP::Class::ResultSet - A list of L objects.
6              
7             =head1 VERSION
8              
9             Version 0.12
10              
11             =cut
12              
13 1     1   3302 use version; our $VERSION = qv("0.11");
  1         2859  
  1         7  
14              
15             =head1 SYNOPSIS
16              
17             use SNMP::Class::ResultSet;
18              
19             my $foo = SNMP::Class::ResultSet->new;
20             $foo->push($vb1);
21            
22             ...
23            
24             #later:
25             ...
26              
27             =cut
28              
29 1     1   91 use warnings;
  1         3  
  1         30  
30 1     1   5 use strict;
  1         2  
  1         28  
31 1     1   6 use Carp;
  1         2  
  1         79  
32 1     1   559 use SNMP;
  0            
  0            
33             use SNMP::Class;
34             use Data::Dumper;
35             use UNIVERSAL qw(isa);
36             use Class::Std;
37              
38             use Log::Log4perl qw(:easy);
39             my $logger = get_logger();
40              
41             use overload
42             '@{}' => \&varbinds,
43             '.' => \&dot,
44             '+' => \&plus,
45             fallback => 1;
46              
47             #class fields
48             my (%varbinds,%index_object,%index_instance,%index_value,%index_oid) : ATTRS();
49              
50              
51             =head1 METHODS
52              
53             B All the methods that are returning a ResultSet will only do so when called in scalar context. They alternatively return the list of varbinds in list context.
54              
55             =head2 new
56              
57             Constructor. Just issue it without arguments. Creates an empty ResultSet. SNMP::Class::Varbind objects can later be stored in there using the push method.
58              
59             =cut
60              
61             sub BUILD {
62             my ($self, $id, $arg_ref) = @_;
63             $varbinds{$id} = [];
64             $index_oid{$id} = {};
65             $index_object{$id} = {};
66             $index_instance{$id} = {};
67             $index_value{$id} = {};
68             }
69              
70             =head2 smart_return
71              
72             In scalar context, this method returns the object itself, while in list context returns the list of the varbinds. In null context, it will croak. This method is mainly used for internal purposes.
73              
74             =cut
75              
76             sub smart_return {
77              
78             defined(my $self = shift(@_)) or croak "Incorrect call to smart_return";
79              
80             defined(my $context = wantarray) or croak "ResultSet used in null context";
81              
82             if ($context) {
83             DEBUG "List context detected";
84             return @{$self->varbinds};
85             }
86              
87             return $self;
88             }
89              
90             =head2 varbinds
91              
92             Returns a reference to a list containing all the stored varbinds. Modifying the list alters the object.
93              
94             =cut
95              
96             sub varbinds {
97             my $self = shift(@_) or croak "Incorrect call to varbind";
98             my $id = ident $self;
99             return $varbinds{$id};
100             }
101              
102              
103              
104             sub index_oid {
105             my $self = shift(@_) or croak "Incorrect call to index_oid";
106             my $id = ident $self;
107             return $index_oid{$id};
108             }
109              
110             sub index_object {
111             my $self = shift(@_) or croak "Incorrect call to index_object";
112             my $id = ident $self;
113             return $index_object{$id};
114             }
115              
116             sub index_instance {
117             my $self = shift(@_) or croak "Incorrect call to index_instance";
118             my $id = ident $self;
119             return $index_instance{$id};
120             }
121              
122             sub index_value {
123             my $self = shift(@_) or croak "Incorrect call to index_value";
124             my $id = ident $self;
125             return $index_value{$id};
126             }
127              
128              
129             =head2 dump
130              
131             Returns a string representation of the entire ResultSet. Mainly used for debugging purposes.
132              
133             =cut
134              
135             sub dump {
136             my $self = shift(@_);
137             croak "Incorrect call to dump" unless defined($self);
138             return join("\n",($self->map(sub {$_->dump})));
139             }
140              
141             =head2 push
142              
143             Takes one argument, which must be an L or a descendant of that class. Inserts it into the ResultSet.
144              
145             =cut
146            
147             sub push {
148             my $self = shift(@_) or croak "Incorrect call to push";
149             my $id = ident $self;
150             my $payload = shift(@_);
151              
152             #make sure that this is of the correct class
153             if (! eval $payload->isa('SNMP::Class::Varbind')) {
154             confess "Payload is not an SNMP::Class::Varbind";
155             }
156             push @{$self->varbinds},($payload);
157             $self->index_oid->{$payload->numeric} = \$payload;
158             #@#push @{$self->index_object->{$payload->object->numeric}},(\$payload);
159             #@#push @{$self->index_instance->{$payload->instance->numeric}},(\$payload);
160             #@#push @{$self->index_value->{$payload->raw_value}},(\$payload);
161            
162             #using the get_oid inside a hash key will force it to use the overloaded '""' quote_oid subroutine
163             ###$self->{oid_index}->{$payload->get_oid}->{$payload->get_instance_numeric} = \$payload;
164            
165             }
166              
167             =head2 pop
168              
169             Pops a varbind out of the Set. Takes no arguments.
170              
171             =cut
172              
173             sub pop {
174             my $self = shift(@_) or croak "Incorrect call";
175             return pop @{$self->varbinds};
176             }
177              
178              
179              
180             #take a list with possible duplicate elements
181             #return a list with each element unique
182             #sub unique {
183             # my @ret;
184             # for my $elem (@_) {
185             # next unless defined($elem);
186             # CORE::push @ret,($elem) if(!(grep {$elem == $_} @ret));#make sure the the == operator does what you expect
187             # }
188             # return @ret;
189             #}
190              
191              
192             #this function (this is not a method) takes an assorted list of SNMP::Class::OIDs, SNMP::Class::ResultSets and even strings
193             #and returns a proper list of SNMP::Class::OIDs. Used for internal purposes.
194             sub construct_matchlist {
195             my @matchlist;
196             for my $item (@_) {
197             if(ref($item)) {
198             if ( eval $item->isa("SNMP::Class::OID") ) {
199             CORE::push @matchlist,($item);
200             }
201             elsif (eval $item->isa('SNMP::Class::ResultSet')) {
202             CORE::push @matchlist,(@{$item->varbinds});
203             }
204             else {
205             croak "I don't know how to handle a ".ref($item);
206             }
207             }
208             else {
209             CORE::push @matchlist,(SNMP::Class::OID->new($item));
210             }
211             }
212             return @matchlist;
213             }
214              
215              
216             #4 little handly subroutines to use for matching using various ways
217              
218             sub match_label {
219             my($x,$y) = @_;
220             return unless defined($x->get_label_oid);
221             return unless defined($y->get_label_oid);
222             return $x->get_label_oid->oid_is_equal( $y->get_label_oid );
223             }
224              
225             sub match_instance {
226             my($x,$y) = @_;
227             return unless defined($x->get_label_oid);
228             return unless defined($y->get_label_oid);
229             return $x->get_instance_oid->oid_is_equal( $y->get_instance_oid );
230             }
231              
232             sub match_fulloid {
233             my($x,$y) = @_;
234             return $x->oid_is_equal( $y );
235             }
236              
237             sub match_value {
238             my($x,$y) = @_;
239             return $x->value eq $y;
240             }
241              
242             #this is the core of the filtering mechanism
243             #the match_callback method may be used as an argument to the filter method
244             #takes 2 arguments:
245             #1)a reference to a comparing subref which returns true or false (see 4 ready match_* subrefs above)
246             #2)a list of items to match against.
247             #produces a closure that matches $_ against any of those items (grep-style) using the comparing subref
248             sub match_callback {
249             my $match_sub_ref = shift(@_);
250             my @matchlist = (@_);
251             confess "Please do not supply empty matchlists in your filters -- completely pointless" unless @matchlist;
252             return sub {
253             for my $match_item (@matchlist) {
254             if ($match_sub_ref->($_,$match_item)) {
255             DEBUG "Item ".$_->to_string." matches";
256             return 1;
257             }
258             }
259             return;
260             };
261             }
262              
263              
264             sub filter_label {
265             my $self = shift(@_) or croak "Incorrect call to label";
266             return $self->filter(match_callback(\&match_label,construct_matchlist(@_)));
267             }
268             sub filter_instance {
269             my $self = shift(@_) or croak "Incorrect call to label";
270             return $self->filter(match_callback(\&match_instance,construct_matchlist(@_)));
271             }
272             sub filter_fulloid {
273             my $self = shift(@_) or croak "Incorrect call to label";
274             return $self->filter(match_callback(\&match_fulloid,construct_matchlist(@_)));
275             }
276             sub filter_value {
277             my $self = shift(@_) or croak "Incorrect call to label";
278             return $self->filter(match_callback(\&match_value,@_));
279             }
280              
281             =head2 filter
282              
283             filter can be used when there is the need to filter the varbinds inside the resultset using arbitrary rules. Takes one argument, which is a reference to a subroutine which will be doing the filtering. The subroutine must return an appropriate true or false value just like in L. The value of each L item in the ResultSet gets assigned to the $_ global variable. For example:
284              
285             print $rs->filter(sub {$_->get_label_oid == 'sysName'});
286              
287             If used in a scalar context, a reference to a new ResultSet containing the filter results will be returned. If used in a list context, a simple array containing the varbinds of the result will be returned. Please do note that in the previous example, the print function always forces list context, so we get what we want.
288              
289             =cut
290              
291             sub filter {
292             my $self = shift(@_) or croak "Incorrect call";
293             my $coderef = shift(@_);
294             if(ref($coderef) ne 'CODE') {
295             confess "First argument must be always a reference to a sub";
296             }
297             my $ret_set = SNMP::Class::ResultSet->new;
298             map { $ret_set->push($_); } ( grep { &$coderef; } @{$self->varbinds} );
299            
300             $ret_set->smart_return;
301             }
302              
303             =head2 find
304              
305             Filters based on key-value pairs that are labels and values. For example:
306              
307             $rs->find('ifDescr' => 'eth0', ifDescr => 'eth1');
308              
309             will find which are the instance oids of the row that has ifDescr equal to 'eth0' B 'eth1' (if any), and filter using that instances.
310              
311             This means that to get the ifSpeed of eth0, one can simply issue:
312            
313             my $speed = $rs->find('ifDescr' => 'eth0')->ifSpeed->value;
314              
315             =cut
316            
317             sub find {
318             my $self = shift(@_) or croak "Incorrect call to find";
319              
320             my @matchlist = ();
321             ###print Dumper(@_);
322            
323             while(1) {
324             my $object = shift(@_);
325             last unless defined($object);
326             my $value = shift(@_);
327             last unless defined($value);
328             DEBUG "Searching for instances with $object == $value";
329             CORE::push @matchlist,(@{$self->filter_label($object)->filter_value($value)});
330             }
331            
332             #be careful. The matchlist which we have may very well be empty!
333             #we should not be filtering against an empty matchlist
334             #note that the filter_instance will croak in such a case.
335             return $self->filter_instance(@matchlist);
336             }
337              
338              
339             =head2 number_of_items
340              
341             Returns the number of items present inside the ResultSet
342              
343             =cut
344              
345             sub number_of_items {
346             my $self = shift(@_) or croak "Incorrect call to number_of_items";
347             return scalar @{$self->varbinds};
348             }
349              
350             =head2 is_empty
351              
352             Reveals whether the ResultSet is empty or not.
353              
354             =cut
355              
356             sub is_empty {
357             my $self = shift(@_) or croak "Incorrect call to is_empty";
358             return ($self->number_of_items == 0);
359             }
360              
361              
362             =head2 dot
363              
364             The dot method overloads the '.' operator, returns L. Use it to get a single L out of a ResultSet as a final instance filter. For example, if $rs contains ifSpeed.1, ifSpeed.2 and ifSpeed.3, then this call:
365              
366             $rs.3
367            
368             returns the ifSpeed.3 L.
369              
370             B
371              
372             =cut
373            
374             sub dot {
375             my $self = shift(@_) or croak "Incorrect call to dot";
376             my $str = shift(@_); #we won't test because it could be false, e.g. ifName.0
377            
378             $logger->debug("dot called with $str as argument");
379              
380             #we force scalar context
381             my $ret = scalar $self->filter_instance($str);
382              
383             if ($ret->is_empty) {
384             confess "empty resultset";
385             }
386             if ($ret->number_of_items > 1) {
387             carp "Warning: resultset with more than 1 items";
388             }
389             return $ret->item(0);
390             }
391              
392             =head2 item
393              
394             Returns the item of the ResultSet with index same as the first argument. No argument yields the first item (index 0) in the ResultSet.
395              
396             =cut
397            
398             sub item {
399             my $self = shift(@_) or croak "Incorrect call";
400             my $index = shift(@_) || 0;
401             return $self->varbinds->[$index];
402             }
403              
404             #calls named method $method on the and hopefully only existing item. Should not be used by the user.
405             #This is an internal shortcut to simplify method creation that applies to SNMP::Class::OID single members of a ResultSet
406             sub item_method :RESTRICTED() {
407             my $self = shift(@_) or croak "Incorrect call";
408             my $method = shift(@_) or croak "missing method name";
409             my @rest = (@_);
410             if($self->is_empty) {
411             croak "$method cannot be called on an empty result set";
412             }
413             if ($self->number_of_items > 1) {
414             WARN "Warning: Calling $method on a result set that has more than one item";
415             }
416             return $self->item(0)->$method(@rest);
417             }
418              
419             #warning: plus will not protect you from duplicates
420             #plus will return a new object
421             sub plus {
422             my $self = shift(@_) or croak "Incorrect call to plus";
423             my $item = shift(@_) or croak "Argument to add(+) missing";
424              
425             #check that this object is an SNMP::Class::Varbind
426             confess "item to add is not an SNMP::Class::ResultSet!" unless (ref($item)&&(eval $item->isa("SNMP::Class::ResultSet")));
427              
428             my $ret = SNMP::Class::ResultSet->new();
429              
430             map { $ret->push($_) } (@{$self->varbinds});
431             map { $ret->push($_) } (@{$item->varbinds});
432              
433             return $ret;
434             }
435              
436             #append act on $self
437             sub append {
438             my $self = shift(@_) or croak "Incorrect call to append";
439             my $item = shift(@_) or croak "Argument to append missing";
440             #check that this object is an SNMP::Class::Varbind
441             confess "item to add is not an SNMP::Class::ResultSet!" unless (ref($item)&&(eval $item->isa("SNMP::Class::ResultSet")));
442             map { $self->push($_) } (@{$item->varbinds});
443             return;
444             }
445              
446             sub map {
447             my $self = shift(@_) or croak "Incorrect call";
448             my $func = shift(@_) or croak "missing sub";
449             croak "argument should be code reference" unless (ref $func eq 'CODE');
450             #$logger->debug("mapping....");
451             my @result;
452             for(@{$self->varbinds}) {
453             #$logger->debug("executing sub with ".$_->dump);
454             CORE::push @result,($func->());
455             }
456             return @result;
457             }
458              
459              
460             sub AUTOMETHOD {
461             my $self = shift(@_) or confess("Incorrect call to AUTOMETHOD");
462             my $id = shift(@_) or confess("Second argument (id) to AUTOMETHOD missing");
463             my $subname = $_; # Requested subroutine name is passed via $_;
464            
465             if (SNMP::Class::Utils::is_valid_oid($subname)) {
466             $logger->debug("ResultSet: $subname seems like a valid OID ");
467             return sub {
468             # if(wantarray) {
469             # $logger->debug("$subname called in list context");
470             # return @{$self->filter_label($subname)->varbinds};
471             # }
472             DEBUG "Returning the resultset";
473             return $self->filter_label($subname);
474             };
475              
476             }
477             elsif (SNMP::Class::Varbind->can($subname)) {
478             DEBUG "$subname method call was refering to the contained varbind. Will delegate to the first item. Resultset is ".$self->dump;
479             return sub { return $self->item_method($subname,@_) };
480             }
481             else {
482             $logger->debug("$subname doesn't seem like something I can actually make sense of. .");
483             return;
484             }
485            
486             #we'll just have to create this little closure and return it to the Class::Std module
487             #remember: this closure will run in the place of the method that was called by the invoker
488              
489             }
490            
491            
492              
493              
494             =head1 AUTHOR
495              
496             Athanasios Douitsis, C<< >>
497              
498             =head1 BUGS
499              
500             Please report any bugs or feature requests to
501             C, or through the web interface at
502             L.
503             I will be notified, and then you'll automatically be notified of progress on
504             your bug as I make changes.
505              
506             =head1 SUPPORT
507              
508             You can find documentation for this module with the perldoc command.
509              
510             perldoc SNMP::Class
511              
512             You can also look for information at:
513              
514             =over 4
515              
516             =item * AnnoCPAN: Annotated CPAN documentation
517              
518             L
519              
520             =item * CPAN Ratings
521              
522             L
523              
524             =item * RT: CPAN's request tracker
525              
526             L
527              
528             =item * Search CPAN
529              
530             L
531              
532             =back
533              
534             =head1 ACKNOWLEDGEMENTS
535              
536             =head1 COPYRIGHT & LICENSE
537              
538             Copyright 2007 Athanasios Douitsis, all rights reserved.
539              
540             This program is free software; you can redistribute it and/or modify it
541             under the same terms as Perl itself.
542              
543             =cut
544              
545             1; # End of SNMP::Class::ResultSet