File Coverage

blib/lib/SIAM/Driver/Simple.pm
Criterion Covered Total %
statement 188 238 78.9
branch 43 60 71.6
condition n/a
subroutine 19 24 79.1
pod 15 15 100.0
total 265 337 78.6


line stmt bran cond sub pod time code
1             package SIAM::Driver::Simple;
2              
3 2     2   3526 use warnings;
  2         13  
  2         67  
4 2     2   12 use strict;
  2         3  
  2         55  
5              
6 2     2   1332 use YAML ();
  2         13411  
  2         41  
7 2     2   267 use Log::Handler;
  2         7  
  2         27  
8 2     2   164 use Digest::MD5 ();
  2         4  
  2         33  
9 2     2   2217 use File::stat;
  2         17022  
  2         13  
10              
11             =head1 NAME
12              
13              
14             SIAM::Driver::Simple - a reference implementation of SIAM Driver
15              
16              
17             =cut
18              
19              
20             =head1 SYNOPSIS
21              
22             The driver does not connect to any external databases. Instead, it reads
23             all the SIAM objects from its YAML data file.
24              
25             The top level element in the data file is expected to be an array of
26             objects that are contained in the SIAM root. The following object
27             classes are expected to be contained by the root object:
28              
29             =over 4
30              
31             =item * SIAM::Contract
32              
33             =item * SIAM::AccessScope
34              
35             =item * SIAM::User
36              
37             =item * SIAM::Device
38              
39             =item * SIAM::Attribute
40              
41             =back
42              
43             Each object definition may have an entry with the key C<_contains_>
44             which points to an array of contained objects. For example, an
45             C object is expected to contain one or more
46             C objects.
47              
48             If a key starts with C<_compute_>, it represents a computable for a
49             given object.
50              
51             All other keys in the object entry define the object attributes. The
52             values are expected to be strings and numbers. The data file should
53             define all the attributes, including C and
54             C.
55              
56             See the file I in SIAM package distribution
57             for reference.
58              
59              
60             =head1 MANDATORY METHODS
61              
62             The following methods are required by C.
63              
64              
65             =head2 new
66              
67             Instantiates a new driver object. The method expects a hashref
68             containing the attributes, as follows:
69              
70             =over 4
71              
72             =item * Logger
73              
74             The logger object is supplied by SIAM.
75              
76             =item * Datafile
77              
78             Full path of the YAML data file which defines all the objects for this driver.
79              
80             =back
81              
82             =cut
83              
84             sub new
85             {
86 1     1 1 5 my $class = shift;
87 1         3 my $drvopts = shift;
88              
89 1         3 my $self = {};
90 1         5 bless $self, $class;
91              
92 1         8 $self->{'logger'} = $drvopts->{'Logger'};
93 1 50       7 die('Logger is not supplied to the driver')
94             unless defined($self->{'logger'});
95            
96 1         3 foreach my $param ('Datafile')
97             {
98 1 50       7 if( not defined($drvopts->{$param}) )
99             {
100 0         0 $self->error('Missing mandatiry parameter ' . $param .
101             ' in SIAM::Driver::Simple->new()');
102 0         0 return undef;
103             }
104             }
105              
106 1         5 $self->{'datafile'} = $drvopts->{'Datafile'};
107            
108 1 50       42 if( not -r $self->{'datafile'} )
109             {
110 0         0 $self->error('Data file is not readable: ' . $self->{'datafile'});
111 0         0 return undef;
112             }
113            
114 1         10 return $self;
115             }
116              
117              
118             =head2 connect
119              
120             Reads the YAML data file
121              
122             =cut
123              
124             sub connect
125             {
126 1     1 1 3 my $self = shift;
127              
128 1         8 $self->debug('Connecting SIAM::Driver::Simple driver to data file: ' .
129             $self->{'datafile'});
130              
131 1         22 my $st = stat($self->{'datafile'});
132 1         254 $self->{'datafile_lastmod'} = $st->mtime();
133            
134 1         9 my $data = eval { YAML::LoadFile($self->{'datafile'}) };
  1         7  
135 1 50       219623 if( $@ )
136             {
137 0         0 $self->error('Cannot load YAML data from ' .
138             $self->{'datafile'} . ': ' . $@);
139 0         0 return undef;
140             }
141            
142 1 50       8 if( ref($data) ne 'ARRAY' )
143             {
144 0         0 $self->error('Top level is not a sequence in ' . $self->{'datafile'});
145 0         0 return undef;
146             }
147              
148 1         5 $self->{'objects'} = {};
149 1         6 $self->{'cont_attr_index'} = {};
150 1         4 $self->{'attr_index'} = {};
151 1         3 $self->{'contains'} = {};
152 1         6 $self->{'container'} = {};
153 1         3 $self->{'data_ready'} = 1;
154 1         4 $self->{'computable_cache'} = {};
155            
156 1         3 foreach my $obj (@{$data})
  1         5  
157             {
158 21         47 $self->_import_object($obj, 'SIAM.ROOT');
159             }
160            
161 1         87 return $self->{'data_ready'};
162             }
163              
164             # recursively import the objects
165              
166             sub _import_object
167             {
168 59     59   74 my $self = shift;
169 59         66 my $obj = shift;
170 59         68 my $container_id = shift;
171              
172 59         333 my $id = $obj->{'siam.object.id'};
173 59 50       115 if( not defined($id) )
174             {
175 0         0 $self->error($container_id .
176             ' contains an object without "siam.object.id"' );
177 0         0 $self->{'data_ready'} = 0;
178 0         0 return;
179             }
180              
181 59         86 my $class = $obj->{'siam.object.class'};
182 59 50       106 if( not defined($class) )
183             {
184 0         0 $self->error('Object ' . $id . ' does not have "siam.object.class"' );
185 0         0 $self->{'data_ready'} = 0;
186 0         0 return;
187             }
188            
189             # duplicate all attributes except "_contains_"
190              
191 59         80 my $dup = {};
192 59         78 while( my ($key, $val) = each %{$obj} )
  376         1259  
193             {
194 317 100       743 if( $key ne '_contains_' )
195             {
196 298         648 $dup->{$key} = $val;
197             $self->{'cont_attr_index'}{$class}{$container_id}{
198 298         1599 $key}{$val}{$id} = 1;
199 298         1756 $self->{'attr_index'}{$class}{$key}{$val}{$id} = 1;
200             }
201             }
202            
203 59         195 $self->{'objects'}{$id} = $dup;
204 59         198 $self->{'contains'}{$container_id}{$class}{$id} = 1;
205 59         114 $self->{'container'}{$id} = $container_id;
206            
207 59 100       221 if( defined($obj->{'_contains_'}) )
208             {
209 19         19 foreach my $contained_obj (@{$obj->{'_contains_'}})
  19         47  
210             {
211 38         221 $self->_import_object($contained_obj, $id);
212             }
213             }
214             }
215              
216              
217             =head2 disconnect
218              
219             Disconnects the driver from its underlying databases.
220              
221             =cut
222              
223             sub disconnect
224             {
225 0     0 1 0 my $self = shift;
226            
227 0         0 delete $self->{'objects'};
228 0         0 delete $self->{'attr_index'};
229 0         0 delete $self->{'cont_attr_index'};
230 0         0 delete $self->{'contains'};
231 0         0 delete $self->{'container'};
232 0         0 delete $self->{'computable_cache'};
233 0         0 $self->{'data_ready'} = 0;
234             }
235              
236              
237             =head2 fetch_attributes
238              
239             $status = $driver->fetch_attributes($attrs);
240              
241             Retrieve the object by ID and populate the hash with object attributes.
242              
243             =cut
244              
245             sub fetch_attributes
246             {
247 114     114 1 133 my $self = shift;
248 114         124 my $obj = shift;
249              
250 114         164 my $id = $obj->{'siam.object.id'};
251 114 50       238 if( not defined($id) )
252             {
253 0         0 $self->error('siam.object.id is not specified in fetch_attributes' );
254 0         0 return undef;
255             }
256            
257 114 50       393 if( not defined($self->{'objects'}{$id}) )
258             {
259 0         0 $self->error('Object not found: ' . $id );
260 0         0 return undef;
261             }
262              
263 114         138 while( my($key, $val) = each %{$self->{'objects'}{$id}} )
  673         2897  
264             {
265 559 100       1265 if( $key !~ /^_compute_/o )
266             {
267 557         1562 $obj->{$key} = $val;
268             }
269             }
270            
271 114         454 return 1;
272             }
273            
274              
275             =head2 fetch_computable
276              
277             $value = $driver->fetch_computable($id, $key);
278              
279             Retrieve a computable. Return empty string if unsupported.
280              
281             =cut
282              
283             sub fetch_computable
284             {
285 3     3 1 5 my $self = shift;
286 3         5 my $id = shift;
287 3         4 my $key = shift;
288              
289 3         10 my $obj = $self->{'objects'}{$id};
290 3 50       10 if( not defined($obj) )
291             {
292 0         0 $self->error('Object not found: ' . $id );
293 0         0 return undef;
294             }
295              
296 3 100       8 if( $key eq 'siam.contract.content_md5hash' )
297             {
298 2 50       8 if( $obj->{'siam.object.class'} eq 'SIAM::Contract' )
299             {
300 2         11 my $st = stat($self->{'datafile'});
301 2 50       362 if( $st->mtime() != $self->{'datafile_lastmod'} )
    50          
302             {
303 0         0 $self->disconnect();
304 0         0 $self->connect();
305             }
306             elsif( defined($self->{'computable_cache'}{$key}) )
307             {
308 0         0 return $self->{'computable_cache'}{$key};
309             }
310            
311 2         36 my $md5 = new Digest::MD5;
312 2         7 $self->_object_content_md5($id, $md5);
313 2         12 my $ret = $md5->hexdigest();
314 2         7 $self->{'computable_cache'}{$key} = $ret;
315 2         21 return $ret;
316             }
317             }
318             else
319             {
320 1         6 my $val = $self->{'objects'}{$id}{'_compute_' . $key};
321 1 50       5 if( defined($val) )
322             {
323 1         5 return $val;
324             }
325             }
326            
327 0         0 return '';
328             }
329            
330              
331             # recursively add all contained objects for MD5 calculation
332             sub _object_content_md5
333             {
334 20     20   27 my $self = shift;
335 20         24 my $id = shift;
336 20         20 my $md5 = shift;
337              
338 20         44 my $obj = $self->{'objects'}{$id};
339            
340 20         23 foreach my $attr (sort keys %{$obj})
  20         124  
341             {
342 141         790 $md5->add('#' . $attr . '//' . $obj->{$attr} . '#');
343             }
344              
345 20 100       115 if( defined($self->{'contains'}{$id}) )
346             {
347 12         14 foreach my $class (sort keys %{$self->{'contains'}{$id}})
  12         47  
348             {
349 14         17 foreach my $contained_id (sort
  14         58  
350             keys %{$self->{'contains'}{$id}{$class}})
351             {
352 18         42 $self->_object_content_md5($contained_id, $md5);
353             }
354             }
355             }
356             }
357            
358              
359              
360             =head2 fetch_contained_object_ids
361              
362             $ids = $driver->fetch_contained_object_ids($id, 'SIAM::Contract', {
363             'match_attribute' => [ 'siam.object.access_scope_id',
364             ['SCOPEID01', 'SCOPEID02'] ]
365             }
366             );
367              
368             Retrieve the contained object IDs.
369              
370             =cut
371              
372             sub fetch_contained_object_ids
373             {
374 55     55 1 77 my $self = shift;
375 55         68 my $container_id = shift;
376 55         63 my $class = shift;
377 55         56 my $options = shift;
378              
379 55         77 my $ret = [];
380              
381 55 100       123 if( defined($options) )
382             {
383 20 50       47 if( defined($options->{'match_attribute'}) )
384             {
385 20         22 my ($filter_attr, $filter_val) = @{$options->{'match_attribute'}};
  20         40  
386            
387 20         25 foreach my $val (@{$filter_val})
  20         37  
388             {
389 20         28 push(@{$ret},
390 20         19 keys %{$self->{'cont_attr_index'}{$class}{$container_id}{
391 20         173 $filter_attr}{$val}});
392             }
393              
394 20         68 return $ret;
395             }
396             }
397            
398 35 50       132 if( defined($self->{'contains'}{$container_id}{$class}) )
399             {
400 35         42 push(@{$ret}, keys %{$self->{'contains'}{$container_id}{$class}});
  35         47  
  35         194  
401             }
402              
403 35         104 return $ret;
404             }
405              
406              
407              
408             =head2 fetch_contained_classes
409              
410             $classes = $driver->fetch_contained_classes($id);
411              
412             Returns arrayref with class names.
413              
414             =cut
415              
416             sub fetch_contained_classes
417             {
418 60     60 1 81 my $self = shift;
419 60         66 my $id = shift;
420              
421 60         80 my $ret = [];
422 60 100       219 if( defined($self->{'contains'}{$id}) )
423             {
424 20         54 foreach my $class (sort keys %{$self->{'contains'}{$id}})
  20         92  
425             {
426 25         27 push(@{$ret}, $class);
  25         72  
427             }
428             }
429 60         125 return $ret;
430             }
431              
432              
433             =head2 fetch_container
434              
435             $attr = $driver->fetch_container($id);
436              
437             Retrieve the container ID and class.
438              
439             =cut
440              
441             sub fetch_container
442             {
443 2     2 1 4 my $self = shift;
444 2         3 my $id = shift;
445              
446 2         11 my $container_id = $self->{'container'}{$id};
447 2 50       8 if( not defined($container_id) )
448             {
449 0         0 return undef;
450             }
451              
452 2         6 my $ret = {'siam.object.id' => $container_id};
453 2 100       7 if( $container_id ne 'SIAM.ROOT' )
454             {
455 1         6 $ret->{'siam.object.class'} =
456             $self->{'objects'}{$container_id}{'siam.object.class'};
457             }
458            
459 2         7 return $ret;
460             }
461              
462              
463             =head2 fetch_object_ids_by_attribute
464              
465             $list = $driver->fetch_object_ids_by_attribute($classname, $attr, $value);
466              
467             Returns a list of object IDs which match the attribute value.
468              
469             =cut
470              
471             sub fetch_object_ids_by_attribute
472             {
473 0     0 1 0 my $self = shift;
474 0         0 my $class = shift;
475 0         0 my $attr = shift;
476 0         0 my $value = shift;
477              
478 0         0 return [keys %{$self->{'attr_index'}{$class}{$attr}{$value}}];
  0         0  
479             }
480            
481              
482              
483             =head2 set_condition
484              
485             The method does nothing in this driver, but only issues a debug message.
486              
487             =cut
488              
489             sub set_condition
490             {
491 0     0 1 0 my $self = shift;
492 0         0 my $id = shift;
493 0         0 my $key = shift;
494 0         0 my $value = shift;
495              
496 0         0 $self->debug('set_condition is called for ' . $id . ': (' .
497             $key . ', ' . $value . ')');
498             }
499              
500              
501             =head2 manifest_attributes
502              
503             The method returns an arrayref with all known attribute names.
504              
505             =cut
506              
507             sub manifest_attributes
508             {
509 1     1 1 2 my $self = shift;
510              
511             # avoid duplicates and skip siam.* attributes
512 1         2 my %manifest;
513 1         2 while(my ($class, $r1) = each %{$self->{'attr_index'}})
  13         48  
514             {
515 12         13 while(my ($attr, $r2) = each %{$r1})
  89         235  
516             {
517 77 100       184 if( $attr !~ /^siam\./o )
518             {
519 20         46 $manifest{$attr} = 1;
520             }
521             }
522             }
523              
524 1         23 return [keys %manifest];
525             }
526              
527              
528              
529              
530             =head1 ADDITIONAL METHODS
531              
532             The following methods are not in the Specification.
533              
534              
535             =head2 debug
536              
537             Prints a debug message to the logger.
538              
539             =cut
540              
541             sub debug
542             {
543 1     1 1 1 my $self = shift;
544 1         3 my $msg = shift;
545 1         18 $self->{'logger'}->debug($msg);
546             }
547              
548              
549             =head2 error
550              
551             Prints an error message to the logger.
552              
553             =cut
554              
555             sub error
556             {
557 0     0 1 0 my $self = shift;
558 0         0 my $msg = shift;
559 0         0 $self->{'logger'}->error($msg);
560             }
561              
562              
563             =head2 object_exists
564              
565             Takes an object ID and returns true if such object is present in the database.
566              
567             =cut
568              
569             sub object_exists
570             {
571 0     0 1 0 my $self = shift;
572 0         0 my $id = shift;
573 0         0 return defined($self->{'objects'}{$id});
574             }
575              
576              
577             =head2 clone_data
578              
579             $data = SIAM::Driver::Simple->clone_data($siam, $callback);
580              
581             The method takes a SIAM object and a callback reference. It walks
582             through the SIAM data and produces a clone suitable for
583             storing into a YAML file and re-using with C.
584              
585             The callback is a sub reference which is supplied with the object ID as
586             an argument. Only the objects which result in true value are being
587             cloned.
588              
589             The method is usable for producing a test data out of productive system.
590              
591             =cut
592              
593             sub clone_data
594             {
595 1     1 1 1270 my $class = shift;
596 1         3 my $siam = shift;
597 1         2 my $filter_callback = shift;
598              
599 1         5 return $class->_retrieve_object_data($siam, $filter_callback);
600             }
601              
602             # recursively walk the objects
603            
604             sub _retrieve_object_data
605             {
606 50     50   64 my $class = shift;
607 50         54 my $obj = shift;
608 50         56 my $filter_callback = shift;
609              
610 50         70 my $ret = {};
611              
612 50 100       128 if( not $obj->is_root() )
613             {
614 49         160 my $attrs = $obj->attributes();
615 49         57 while(my($key, $val) = each %{$attrs})
  375         1144  
616             {
617 326         799 $ret->{$key} = $val;
618             }
619             }
620              
621 50         158 my $contained_data = [];
622 50         179 my $classes = $obj->_driver->fetch_contained_classes($obj->id);
623 50         66 foreach my $objclass ( @{$classes} )
  50         92  
624             {
625 18         54 my $objects = $obj->get_contained_objects($objclass);
626 18         25 foreach my $contained_obj (@{$objects})
  18         31  
627             {
628 50 100       59 if( &{$filter_callback}($contained_obj) )
  50         120  
629             {
630 49         206 push(@{$contained_data},
  49         140  
631             $class->_retrieve_object_data($contained_obj,
632             $filter_callback));
633             }
634             }
635             }
636              
637 50 100       137 if( $obj->is_root() )
638             {
639 1         7 return $contained_data;
640             }
641            
642 49 100       65 if( scalar(@{$contained_data}) > 0 )
  49         108  
643             {
644 13         26 $ret->{'_contains_'} = $contained_data;
645             }
646              
647 49         315 return $ret;
648             }
649            
650              
651              
652              
653             =head1 SEE ALSO
654              
655             L, L, L
656              
657             =cut
658              
659             1;
660              
661             # Local Variables:
662             # mode: cperl
663             # indent-tabs-mode: nil
664             # cperl-indent-level: 4
665             # cperl-continued-statement-offset: 4
666             # cperl-continued-brace-offset: -4
667             # cperl-brace-offset: 0
668             # cperl-label-offset: -2
669             # End: