File Coverage

blib/lib/Trinket/Directory.pm
Criterion Covered Total %
statement 158 175 90.2
branch 55 72 76.3
condition 16 24 66.6
subroutine 28 31 90.3
pod 8 16 50.0
total 265 318 83.3


line stmt bran cond sub pod time code
1             ###########################################################################
2             ### Trinket::Directory
3             ###
4             ### Access to directory of persistent objects.
5             ###
6             ### $Id: Directory.pm,v 1.2 2001/02/16 07:21:31 deus_x Exp $
7             ###
8             ### TODO:
9             ### -- Nail down the initial interaction between Directory and
10             ### DataAccess in the init(), open(), create(), close() methods
11             ### -- Callbacks to Object for pre- and post- storage
12             ### -- Callbacks for Object to accomdate on-demand property get/set
13             ### -- Do something meaningful with close()
14             ### -- Further cache logic? Expiration ages? Treat like
15             ### pseudo-transactions, no calls to store() until a commit?
16             ### -- Document caching
17             ### -- Cooperate with ACLs
18             ### -- Implement a cursor for access to search results
19             ### -- Implement support for data types (char is only type for now)
20             ### -- Should DESTROY() do something? (per warning)
21             ###
22             ###########################################################################
23              
24             package Trinket::Directory;
25              
26 1     1   829 use strict;
  1         2  
  1         37  
27 1     1   5 use vars qw($VERSION @ISA @EXPORT $DESCRIPTION $AUTOLOAD);
  1         1  
  1         96  
28 1     1   5 no warnings qw( uninitialized );
  1         2  
  1         86  
29              
30             # {{{ Begin POD
31              
32             =head1 NAME
33              
34             Trinket::Directory - Persistent object management and lookup
35              
36             =head1 SYNOPSYS
37              
38             my $dir = new Trinket::Directory();
39             $dir->create('RAM:test') || die("Creation failed");
40              
41             $dir = new Trinket::Directory();
42             $dir->open('RAM:test') || die("Open failed");
43              
44             $dir = new Trinket::Directory('RAM:test') || die("Open failed.");
45              
46             my $obj = new MyTrinketObjectSubclass({ foo => 'foo_value' });
47              
48             my $obj_id = $dir->store($obj) || die "Storage failed";
49              
50             $obj = $dir->retrieve($obj_id) || die "Retrieval failed.";
51              
52             my @objs = $dir->search('foo=foo_value');
53              
54             my @objs = $dir->search('LDAP', 'foo=foo_value');
55              
56             $dir->delete($obj);
57              
58             $dir->delete($obj_id);
59              
60             $dir->close();
61              
62             =head1 DESCRIPTION
63              
64             Trinket::Directory offers methods for the management and lookup of
65             persistent Perl objects subclassed from Trinket::Object.
66              
67             It provides an interface for object storage,
68             retrieval, deletion, and searching via a set of selectable data access
69             backend modules and search filter parsing modules.
70              
71             Data access modules each encapulate the
72             specifics of data management in a particular medium while
73             Trinket::Directory offers a common interface for object persistent
74             management. This relationship is modeled after the DBI and DBD Perl
75             modules, as in this diagram:
76              
77             =head2 Architecture of an Trinket::Directory Application
78              
79             Trinket::Directory::DataAccess::*
80             | .----------. |
81             .------. .-| RAM |
82             | | | `----------'
83             | | .------------------. | .----------.
84             |Perl |-|Trinket::Directory|-+-|BerkeleyDB|
85             |script| `------------------' | `----------'
86             | | | .----------.
87             | | `-| DBI |
88             `------' `----------'
89              
90             A Perl script creates an instance of Trinket::Directory using a
91             directory descriptor, which includes information on which DataAccess
92             module to use, and options to the selected DataAccess module such as
93             identity of the data source, authentication information, and storage
94             options.
95              
96             As long as the DataAccess module is implemented correctly, the
97             selection of DataAccess is transparent to the Perl script, which makes
98             calls to the methods of Trinket::Directory. Trinket::Directory, in
99             turn, calls upon the selected DataAccess backend to perform the
100             actions specific to a data source to manage object data and lookup.
101              
102             Search filter parser modules handle various formats of object search
103             queries, such as LDAP-style (RFC1960) filters and SQL-like queries. A
104             particular parser may be specified when searching for objects. Some
105             filter parsers may support query formats which more fully exploit the
106             features of a given data access backend. All filter parsers offer a
107             common lowest denominator of features.
108              
109             =cut
110              
111             # }}}
112              
113             # {{{ METADATA
114              
115             BEGIN
116             {
117 1     1   2 $VERSION = "0.0";
118 1         204 @ISA = qw( Exporter );
119 1         26 $DESCRIPTION = 'Base object directory';
120             #@EXPORT = qw(&_META_TYPES &_META_PROP_TYPE &_META_PROP_INDEXED
121             # &_META_PROP_DESC);
122             }
123              
124             # }}}
125              
126 1     1   6 use Trinket::Object;
  1         1  
  1         6  
127 1     1   724 use Trinket::Directory::DataAccess;
  1         3  
  1         55  
128 1     1   882 use Trinket::Directory::FilterParser;
  1         3  
  1         130  
129 1     1   2181 use Storable;
  1         5279  
  1         98  
130 1     1   11 use Carp qw( croak cluck );
  1         3  
  1         101  
131 1     1   1639 use Data::Dumper qw( Dumper );
  1         15734  
  1         187  
132              
133             # {{{ METHODS
134              
135             =head1 METHODS
136              
137             =over 4
138              
139             =cut
140              
141             # }}}
142              
143             # {{{ new(): Object constructor
144              
145             =item $dir = new Trinket::Directory();
146              
147             =item $dir = new Trinket::Directory($dir_desc,$options);
148              
149             Create a Directory instance. The description of a directory and a
150             reference to a hash of additional options may be given to open a data
151             source immediately.
152              
153             Directory descriptions are colon-separated strings listing the
154             DataAccess module, the name of the directory to use, and a list of
155             semi-colon separated option name/value pairs. For example:
156              
157             RAM:test
158             RAM:save_me:file=save_file;cache_objects=0
159             BerkeleyDB:test2:db_home=dbdir
160             DBI:test_objs:user=someone;pass=foo;host=localhost
161              
162             For options available in a DataAccess module which cannot be specified
163             in a string, such as more complex data structures and object
164             references, a hash containing named resources can be supplied as an
165             optional parameter.
166              
167             See the documentation on Trinket::Directory::DataAccess modules for
168             further details.
169              
170             =cut
171              
172             sub new
173             {
174 7     7 1 44 my $class = shift;
175              
176 7         18 my $self = {};
177              
178 7         20 bless($self, $class);
179 7 100       29 $self->init(@_) || return undef;
180 6         26 return $self;
181             }
182              
183             # }}}
184             # {{{ init(): Object initializer
185              
186             sub init
187             {
188 1     1   8 no strict 'refs';
  1         2  
  1         2663  
189 7   50 7 0 47 my ($self, $desc, $props) = (shift, shift, (shift || {}));
190              
191 7         28 $self->{data_access} = undef;
192 7         16 $self->{filter_parser} = undef;
193 7         21 $self->{cache} = [];
194              
195 7 100       21 if (defined $desc)
196 2 100       8 { $self->open($desc, $props) || return undef; }
197              
198 6         23 return 1;
199             }
200              
201             # }}}
202              
203             # {{{ create()
204              
205             =item $dir->create($dir_desc,$options);
206              
207             Prepare the data resources for a new directory. This method must be
208             passed a directory description as described in new(), with an optional
209             reference to a hash of additional resources.
210              
211             This method will return 1 if successful, and will destroy any
212             directory previously identified by the given options. An undef value
213             will be returned on failure.
214              
215             =cut
216              
217             sub create
218             {
219 1   50 1 1 14 my ($self, $desc, $props) = (shift, shift, (shift || {}));
220              
221 1         4 my ($access, $name, $ext_param) = split(/:/, $desc);
222 1         6 my %ext_props = map { split(/=/, $_) } split(/;/, $ext_param);
  0         0  
223 1         3 @$props{keys %ext_props} = values %ext_props;
224              
225 1 50       5 $self->enable_cache() if ($props->{cache_objects});
226 1 50       4 $self->clear_cache() if ($self->{cache_objects});
227              
228 1         5 my $data = $self->{data_access} =
229             $self->get_data_access($access, $props);
230              
231 1   50     8 my $parser_class = $props->{filter_parser} || 'LDAP';
232              
233 1         4 my $parser = $self->{filter_parser} =
234             $self->get_filter_parser($parser_class);
235              
236 1 50       6 return undef if ! $data->create($name, $props);
237              
238 1         6 return 1;
239             }
240              
241             # }}}
242             # {{{ open()
243              
244             =item $dir->open($dir_desc,$options);
245              
246             Acquire the data resources for an existing directory. This method must be
247             passed a directory description as described in new(), with an optional
248             reference to a hash of additional resources.
249              
250             This method will return 1 if successful, and an undef value will be
251             returned on failure (such as if the directory does not yet exist).
252              
253             =cut
254              
255             sub open
256             {
257 5   100 5 1 42 my ($self, $desc, $props) = (shift, shift, (shift || {}));
258              
259 5         22 my ($access, $name, $ext_param) = split(/:/, $desc);
260 5         20 my %ext_props = map { split(/=/, $_) } split(/;/, $ext_param);
  0         0  
261 5         16 @$props{keys %ext_props} = values %ext_props;
262              
263 5 50       14 $self->enable_cache() if ($props->{cache_objects});
264 5 50       18 $self->clear_cache() if ($self->{cache_objects});
265              
266 5         18 my $data = $self->{data_access} =
267             $self->get_data_access($access, $props);
268              
269 5   50     30 my $parser_class = $props->{filter_parser} || 'LDAP';
270              
271 5         19 my $parser = $self->{filter_parser} =
272             $self->get_filter_parser($parser_class);
273              
274 5 100       24 return undef if ! $data->open($name, $props);
275              
276 3         12 return 1;
277             }
278              
279             # }}}
280             # {{{ close()
281              
282             =item $dir->close()
283              
284             Release the data resources for the current opened directory. After
285             this method is called, another directory can be opened, but no further
286             directory operations can made until then.
287              
288             This method returns 1 on success, and undef on any failures.
289              
290             =cut
291              
292             sub close
293             {
294 2     2 1 5 my ($self) = @_;
295              
296 2 50       17 return undef if ! ($self->is_ready());
297              
298 2         23 $self->{data_access}->close();
299              
300 2         36 return 1;
301             }
302              
303             # }}}
304             # {{{ serialize()
305              
306             =item $dir->serialize()
307              
308             If appropriate, return the current opened directory as a serialized
309             binary stream.
310              
311             This method returns data on success, and undef on any failures.
312              
313             =cut
314              
315             sub serialize {
316 0     0 1 0 my ($self) = @_;
317            
318 0 0       0 return undef if ! ($self->is_ready());
319            
320 0         0 return $self->{data_access}->serialize();
321             }
322              
323             # }}}
324             # {{{ deserialize()
325              
326             =item $dir->deserialize()
327              
328             If appropriate, populate the current opened directory with data from a
329             serialized binary stream.
330              
331             This method returns 1 on success, and undef on any failures.
332              
333             =cut
334              
335             sub deserialize {
336 0     0 1 0 my ($self, $data) = @_;
337              
338 0 0       0 if (! ($self->is_ready())) {
339 0         0 return undef;
340             }
341              
342 0         0 return $self->{data_access}->deserialize($data);
343             }
344              
345             # }}}
346              
347             # {{{ store()
348              
349             =item $id = $dir->store($obj);
350              
351             Store a given object into the directory.
352              
353             If the object either has no valid id or has not been stored by this
354             directory before, it will be given a new id and its directory property
355             will be set with a reference to this directory.
356              
357             This method will return the object's method, or undef on failure. '
358              
359             =cut
360              
361             sub store {
362 107     107   711 my ($self, $obj) = @_;
363 107         140 my ($id, $dir, $old_id, $old_dir, $new);
364              
365             ### Return empty-handed if the directory isn't ready.
366 107 100       439 return undef if ! ($self->is_ready());
367              
368             ### Also fail if there's no object passed to us.
369 106 50       313 return undef if !defined $obj;
370              
371 106         204 my $data = $self->{data_access};
372              
373             ### Preserve the object's original id and directory in case we need
374             ### to recover it
375 106         455 $old_id = $obj->get_id();
376 106         470 $old_dir = $obj->get_directory();
377              
378             ### Set the object's directory as ourself.
379 106         1537 $obj->set_directory($self);
380              
381             ### Was this object store before, but by a different directory? Then
382             ### undefine its id and dirty all the properties so we'll handle it as
383             ### a brand new object.
384 106 100 100     401 if ( (defined $old_id) && ( $old_dir ne $self ) ) {
385 2         7 $obj->set_id(undef);
386 2         14 $obj->_dirty_all();
387             }
388              
389             ### If the attempt to store the object is unsuccessful, restore the
390             ### previous id and directory and fail.
391 106 50       508 if (!defined ($id = $data->store_object($obj))) {
392 0         0 $obj->set_id($old_id);
393 0         0 $obj->set_directory($old_dir);
394 0         0 return undef;
395             }
396              
397             ### Cache the object.
398 106 100       816 $self->{cache}->[$id] = $obj if ($self->{cache_objects});
399              
400             ### Clean the object.
401 106         557 $obj->_clean_all();
402              
403 106         460 return $id;
404             }
405              
406             # }}}
407             # {{{ retrieve()
408              
409             =item $obj = $dir->retrieve($id);
410              
411             Retrieve an object by object id.
412              
413             A reference to the object will be returned, or undef on failure.
414              
415             =cut
416              
417             sub retrieve
418             {
419 549     549   870 my ($self, $id) = @_;
420              
421 549 50       1045 return undef if ! ($self->is_ready());
422              
423 549         1113 my $data = $self->{data_access};
424              
425 549 100 66     3243 return $self->{cache}->[$id]
426             if ( (defined $self->{cache}->[$id]) &&
427             ($self->{cache_objects}) );
428              
429 28   100     178 my $obj = $data->retrieve_object($id) || return undef;
430 5         12 my $class = ref($obj);
431             #if (! defined $class::VERSION) {
432 5         440 eval "require $class";
433             #}
434 5         45 $obj->set_id($id);
435 5         23 $obj->set_directory($self);
436              
437             ### Cache the object
438 5 100       26 $self->{cache}->[$obj->get_id()] = $obj
439             if ($self->{cache_objects});
440              
441 5         15 return $obj;
442             }
443              
444             # }}}
445             # {{{ delete()
446              
447             =item $dir->delete($obj);
448              
449             =item $dir->delete($obj_id);
450              
451             Delete a given object or object by id.
452              
453             Note that deleting by id is no more efficient than deleting by object
454             reference, as most data access backends need to retrieve the object
455             anyway in order to complete deletion. Deletion by id is just meant as
456             a shortcut.
457              
458             This method returns 1 on success, and undef on failure.
459              
460             =cut
461              
462             sub delete
463             {
464 6     6 1 677 my ($self, $thing) = @_;
465              
466 6 50       44 return undef if ! ($self->is_ready());
467              
468 6   66     37 my $is_obj = ( ref($thing) && UNIVERSAL::isa($thing, 'Trinket::Object') );
469 6 100       19 my $obj = ($is_obj) ? $thing : undef;
470 6 100       19 my $id = ($is_obj) ? $obj->get_id() : $thing;
471              
472 6         12 my $data = $self->{data_access};
473              
474             ### Fail if we don't have an id by this point.
475 6 50       15 return undef if (!defined $id);
476              
477             ### If we didn't receieve an object...
478 6 100       17 if ( !$is_obj )
479             {
480             ### Get a copy of the object from the cache (assuming it's there)
481 4 100       16 $obj = $self->{cache}->[$id] if ($self->{cache_objects});
482              
483             ### As a last resort, retrieve it from storage.
484 4 100       27 $obj = $self->retrieve($id) if (!defined $obj);
485             }
486              
487             ### If after all that, we still don't have an object, quit.
488 6 100       23 return undef if (!defined $obj);
489              
490             ### Disconnect object references from the directory
491 5         26 $obj->_dirty_all();
492 5         24 $obj->set_id(undef);
493 5         21 $obj->set_directory(undef);
494              
495             ### Delete the object
496 5         28 $data->delete_object($id, $obj);
497              
498             ### Forget about the cached object.
499 5 100       20 $self->{cache}->[$id] = undef
500             if ($self->{cache_objects});
501              
502 5         19 return 1;
503             }
504              
505             # }}}
506             # {{{ search()
507              
508             =item $objs = $dir->search($parser_name,$filter);
509              
510             =item $objs = $dir->search($filter);
511              
512             Search for objects using a given search filter, optionally using a
513             named search filter parser module. By default, the LDAP search filter
514             parser is used.
515              
516             This method returns a list of object references found.
517              
518             =cut
519              
520             sub search {
521 15     15 1 3659 my $self = shift;
522 15         103 my $tmp = shift;
523            
524 15         24 my ($filter, $parser_name);
525 15 50       236 if (@_)
526 0         0 { $filter = shift; $parser_name = $tmp; }
  0         0  
527             else
528 15         22 { $filter = $tmp; $parser_name = 'LDAP'; }
  15         26  
529              
530 15         27 my $data = $self->{data_access};
531 15   33     47 my $parser = $self->get_filter_parser($parser_name) ||
532             $self->{filter_parser};
533            
534 15         69 my $parsed = $parser->parse($filter);
535            
536 15         22 my (@objs, $obj);
537 15         61 foreach ($data->search_objects($parsed)) {
538 543         1017 $obj = $self->retrieve($_);
539 543 100       1466 push @objs, $obj if (defined $obj);
540             }
541 15         348 return @objs;
542             }
543              
544             # }}}
545              
546             # {{{ enable_cache
547              
548             sub enable_cache
549             {
550 1     1 0 3 my ($self) = @_;
551              
552 1         4 $self->{cache_objects} = 1;
553 1         2 return 1;
554             }
555              
556             # }}}
557             # {{{ disable_cache
558              
559             sub disable_cache
560             {
561 0     0 0 0 my ($self) = @_;
562              
563 0         0 $self->{cache_objects} = 0;
564 0         0 return 1;
565             }
566              
567             # }}}
568             # {{{ clear_cache
569              
570             sub clear_cache
571             {
572 2     2 0 332 my ($self) = @_;
573              
574 2         7 $self->{cache} = [];
575             }
576              
577             # }}}
578              
579             # {{{ is_ready():
580              
581             sub is_ready
582             {
583 669     669 0 1088 my $self = shift;
584              
585 669 100       2261 return undef if (!defined $self->{data_access});
586 667         1001 my $data = $self->{data_access};
587 667         2004 return $data->is_ready();
588             }
589              
590             # }}}
591              
592             # {{{ get_data_access
593              
594             sub get_data_access
595             {
596 6     6 0 13 my ($self, $access, $props) = @_;
597              
598 6         14 my $pkg = "Trinket::Directory::DataAccess::$access";
599              
600 6         21 return $self->create_object($pkg, $props);
601             }
602              
603             # }}}
604             # {{{ get_filter_parser
605              
606             sub get_filter_parser
607             {
608 21     21 0 45 my ($self, $parser, $props) = @_;
609              
610 21 100       145 return $self->{parser}->{$parser}
611             if (defined $self->{parser}->{$parser});
612              
613 6         12 my $pkg = "Trinket::Directory::FilterParser::$parser";
614              
615 6         14 return $self->{parser}->{$parser} =
616             $self->create_object($pkg, $props);
617             }
618              
619             # }}}
620             # {{{ create_object
621              
622             sub create_object
623             {
624 12     12 0 20 my ($self, $class, $props) = @_;
625 12         16 my $obj;
626             {
627 1     1   10 no strict 'refs';
  1         2  
  1         195  
  12         16  
628              
629 12 100       18 if ( ! ${$class."::DESCRIPTION"} )
  12         86  
630             {
631 2         186 eval("require $class; import $class;");
632 2 50       15 die ("Could not load $class: $@") if ($@);
633             }
634 12         80 $obj = ("$class")->new($props);
635             }
636              
637 12         56 return $obj;
638             }
639              
640             # }}}
641              
642             # {{{ DESTROY
643              
644             sub DESTROY
645             {
646 5     5   13 my $self = shift;
647              
648 5 100       16 $self->close() if ($self->is_ready())
649             }
650              
651             # }}}
652              
653             # {{{ End POD
654              
655             =back
656              
657             =head1 AUTHOR
658              
659             Maintained by Leslie Michael Orchard >
660              
661             =head1 COPYRIGHT
662              
663             Copyright (c) 2000, Leslie Michael Orchard. All Rights Reserved.
664             This module is free software; you can redistribute it and/or
665             modify it under the same terms as Perl itself.
666              
667             =cut
668              
669             # }}}
670              
671             1;
672             __END__