File Coverage

blib/lib/WE/DB/ObjBase.pm
Criterion Covered Total %
statement 12 205 5.8
branch 0 78 0.0
condition 0 18 0.0
subroutine 4 24 16.6
pod 17 18 94.4
total 33 343 9.6


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # $Id: ObjBase.pm,v 1.16 2004/02/19 22:26:53 eserte Exp $
5             # Author: Slaven Rezic
6             #
7             # Copyright (C) 2002 Slaven Rezic. All rights reserved.
8             # This package is free software; you can redistribute it and/or
9             # modify it under the same terms as Perl itself.
10             #
11             # Mail: slaven@rezic.de
12             # WWW: http://www.rezic.de/eserte/
13             #
14              
15             =head1 NAME
16              
17             WE::DB::ObjBase - base class for WE_Framework object databases
18              
19             =head1 SYNOPSIS
20              
21             use base qw(WE::DB::ObjBase);
22              
23             =head1 DESCRIPTION
24              
25             =cut
26              
27             package WE::DB::ObjBase;
28 15     15   92 use base qw(WE::DB::Base);
  15         27  
  15         8807  
29              
30 15     15   86 use strict;
  15         29  
  15         448  
31 15     15   101 use vars qw($VERSION);
  15         183  
  15         1139  
32             $VERSION = sprintf("%d.%02d", q$Revision: 1.16 $ =~ /(\d+)\.(\d+)/);
33              
34 15     15   9001 use WE::Util::Date;
  15         37  
  15         37692  
35              
36             =head2 METHODS
37              
38             Please see also L for inherited methods.
39              
40             =over
41              
42             =cut
43              
44             =item children($object_id)
45              
46             Like children_ids, but return objects.
47              
48             =cut
49              
50             sub children {
51 0     0 1   my($self, $obj_id) = @_;
52 0           map {
53 0           my $o = $self->get_object($_);
54 0 0         if (!$o) {
55 0           my $obj_id = $obj_id;
56 0           my $child_id = $_;
57 0           $self->idify_params($obj_id, $child_id);
58 0           warn "Inconsistency in children method call for objid=$obj_id detected: child with objid=$child_id non-existent. Consider to run we_fsck. Error";
59 0           ();
60             } else {
61 0           $o;
62             }
63             } $self->children_ids($obj_id);
64             }
65              
66             =item parents($object_id)
67              
68             Like parent_ids, but return parent objects instead.
69              
70             =cut
71              
72             sub parents {
73 0     0 1   my($self, $obj_id) = @_;
74 0           map {
75 0           my $o = $self->get_object($_);
76 0 0         if (!$o) {
77 0           warn "Inconsistency in parents($obj_id) detected";
78 0           ();
79             } else {
80 0           $o;
81             }
82             } $self->parent_ids($obj_id);
83             }
84              
85             =item versions($object_id)
86              
87             Like version_ids, but return version objects instead.
88              
89             =cut
90              
91             sub versions {
92 0     0 1   my($self, $obj_id) = @_;
93 0           map {
94 0           my $o = $self->get_object($_);
95 0 0         if (!$o) {
96 0           warn "Inconsistency in versions($obj_id) detected";
97 0           ();
98             } else {
99 0           $o;
100             }
101             } $self->version_ids($obj_id);
102             }
103              
104             =item objectify_params($id_or_obj, ...)
105              
106             For each parameter in the list, change the argument to be an object of
107             the database.
108              
109             =cut
110              
111             sub objectify_params {
112 0     0 1   my $self = shift;
113 0           foreach (@_) {
114 0 0         if (!UNIVERSAL::isa($_, "WE::Obj")) {
115 0           $_ = $self->get_object($_);
116             }
117             }
118             }
119              
120             =item idify_params($id_or_obj, ...)
121              
122             For each parameter in the list, change the argument to be an object
123             identifier if it was an object, or leave it as it was.
124              
125             =cut
126              
127             sub idify_params {
128 0     0 1   my $self = shift;
129 0           foreach (@_) {
130 0 0         if (UNIVERSAL::isa($_,"WE::Obj")) {
131 0           $_ = $_->Id;
132             }
133             }
134             }
135              
136             =item replace_content_from_file($object_id, $filename)
137              
138             Like replace_content, but get contents from file.
139              
140             =cut
141              
142             sub replace_content_from_file {
143 0     0 1   my($self, $objid, $filename) = @_;
144 0           $self->idify_params($objid);
145 0 0         open(F, $filename) or die "Can't open file $filename: $!";
146 0           local $/ = undef;
147 0           my $new_content = ;
148 0           close F;
149 0           $self->replace_content($objid, $new_content);
150             }
151              
152             =item walk($object_id, $sub_routine, @args)
153              
154             Traverse the object hierarchie, beginning at the object with id
155             C<$object_id>. For each object, C<$sub_routine> is called with the
156             object id and optional C<@args>. Note that the subroutine is B
157             called for the start object itself.
158              
159             If there's no persistent connection to the database (i.e. the database
160             was not accessed with -connect => 1), then using
161             B is advisable for better performance.
162              
163             Here are some examples for using walk.
164              
165             Get the number of descendent objects from the folder with Id
166             C<$folder_id>. The result is in the C<$obj_count> variable:
167              
168             my $obj_count = 0;
169             $objdb->walk($folder_id, sub {
170             my($id, $ref) = @_;
171             $$ref++;
172             }, \$obj_count);
173             warn "There are $obj_count objects in $folder_id\n";
174              
175             Get all released descendant objects. The released state should be
176             recorded in the Release_State member. The resulting list is a flat
177             array.
178              
179             my @results;
180             $objdb->walk($folder_id, sub {
181             my($id) = @_;
182             my $obj = $objdb->get_object($id);
183             if ($obj->Release_State eq 'released') {
184             push @results, $obj;
185             }
186             });
187             # The released objects are in @results.
188              
189             If you want to break the recursion on a condition, simply use an
190             C-block and C on the condition. See the source code of
191             C method for an example.
192              
193             C uses postorder traversal, that is, subtrees first, node later.
194              
195             Note that the start object itself is not included in the traversal and
196             the subroutine will not be called for it.
197              
198             The returned value of the last callback called with be returned.
199              
200             =item walk_preorder($object_id, $sub_routine, @args)
201              
202             This is like C, but uses preorder instead of postorder, that is,
203             node first, children later.
204              
205             Note that the start object itself will be included in the traversal.
206             This is different from the C method.
207              
208             In preorder walk, the traversal of subtrees can be avoided by setting
209             the global variable C<$WE::DB::Obj::prune> to a true value.
210              
211             =cut
212              
213             sub walk {
214 0     0 1   my($self, $objid, $sub_routine, @args) = @_;
215 0           my $ret;
216 0           $self->idify_params($objid);
217 0 0         if (!UNIVERSAL::isa($sub_routine, 'CODE')) {
218 0           die "Second parameter of walk should be code reference";
219             }
220 0           for my $sub_obj_id ($self->children_ids($objid)) {
221 0           $self->walk($sub_obj_id, $sub_routine, @args);
222 0           $ret = $sub_routine->($sub_obj_id, @args);
223             }
224 0           $ret;
225             }
226              
227             sub walk_preorder {
228 0     0 1   my($self, $objid, $sub_routine, @args) = @_;
229 0           my $ret;
230 0           $self->idify_params($objid);
231 0 0         if (!UNIVERSAL::isa($sub_routine, 'CODE')) {
232 0           die "Second parameter of walk_preorder should be code reference";
233             }
234              
235             {
236 0           local $WE::DB::Obj::prune;
  0            
237 0           $ret = $sub_routine->($objid, @args);
238 0 0         return $ret if $WE::DB::Obj::prune;
239             }
240              
241 0           for my $sub_obj_id ($self->children_ids($objid)) {
242 0           $ret = $self->walk_preorder($sub_obj_id, $sub_routine, @args);
243             }
244 0           $ret;
245             }
246              
247             # XXX Document, and implement walk_up_prepostorder when needed!
248             sub walk_prepostorder {
249 0     0 0   my($self, $objid, $pre_sub_routine, $post_sub_routine, @args) = @_;
250 0           my $ret;
251 0           $self->idify_params($objid);
252 0 0 0       if (!UNIVERSAL::isa($pre_sub_routine, 'CODE') ||
253             !UNIVERSAL::isa($post_sub_routine, 'CODE')) {
254 0           die "Second and third parameters of walk_prepostorder should be code references";
255             }
256              
257             {
258 0           local $WE::DB::Obj::prune;
  0            
259 0           $ret = $pre_sub_routine->($objid, @args);
260 0 0         return $ret if $WE::DB::Obj::prune;
261             }
262              
263 0           for my $sub_obj_id ($self->children_ids($objid)) {
264 0           $ret = $self->walk_prepostorder($sub_obj_id, $pre_sub_routine, $post_sub_routine, @args);
265             }
266              
267             {
268 0           local $WE::DB::Obj::prune;
  0            
269 0           $ret = $post_sub_routine->($objid, @args);
270 0 0         return $ret if $WE::DB::Obj::prune;
271             }
272              
273 0           $ret;
274             }
275              
276             =item walk_up($object_id, $sub_routine, @args)
277              
278             Same as C, but walk the tree up, that is, traverse all parents
279             from the object to the root.
280              
281              
282             =item walk_up_preorder($object_id, $sub_routine, @args)
283              
284             Same as C, but traverse in pre-order, that is, from the
285             object to the root. Note that the object itself is also included in
286             the traversal.
287              
288             In preorder walk, the further traversal of parents can be avoided by
289             setting the global variable C<$WE::DB::Obj::prune> to a true value.
290              
291             =cut
292              
293             sub walk_up {
294 0     0 1   my($self, $objid, $sub_routine, @args) = @_;
295 0           my $ret;
296 0           $self->idify_params($objid);
297 0 0         if (!UNIVERSAL::isa($sub_routine, 'CODE')) {
298 0           die "Second parameter of walk_up should be code reference";
299             }
300 0           for my $p_obj_id ($self->parent_ids($objid)) {
301 0           $self->walk_up($p_obj_id, $sub_routine, @args);
302 0           $ret = $sub_routine->($p_obj_id, @args);
303             }
304 0           $ret;
305             }
306              
307             sub walk_up_preorder {
308 0     0 1   my($self, $objid, $sub_routine, @args) = @_;
309 0           my $ret;
310 0           $self->idify_params($objid);
311 0 0         if (!UNIVERSAL::isa($sub_routine, 'CODE')) {
312 0           die "Second parameter of walk_up_preorder should be code reference";
313             }
314              
315 0           local $WE::DB::Obj::prune;
316 0           $ret = $sub_routine->($objid, @args);
317 0 0         return $ret if $WE::DB::Obj::prune;
318              
319 0           for my $p_obj_id ($self->parent_ids($objid)) {
320 0 0         if (defined $p_obj_id) {
321 0           $ret = $self->walk_up_preorder($p_obj_id, $sub_routine, @args);
322             }
323             }
324 0           $ret;
325             }
326              
327             =item whole_tree([$objid])
328              
329             Return the whole (sub)tree of C<$objid>. If C<$objid> is not given,
330             then return the whole tree. The elements of the tree are structured in
331             a nested array. Each element is a hash of the following elements: Id,
332             Title and isFolder.
333              
334             =cut
335              
336             sub whole_tree {
337 0     0 1   my($self, $objid, $tree) = @_;
338 0 0         $objid = $self->root_object->id if !defined $objid;
339 0 0         $tree = [] if !$tree;
340 0           my $obj = $self->get_object($objid);
341 0 0         if (!$obj) {
342 0           warn "Can't get object $objid!";
343 0           return;
344             }
345 0           push @$tree, {Id=>$obj->Id, Title=>$obj->Title, isFolder=>$obj->is_folder};
346 0           my @children_ids = $self->children_ids($objid);
347 0 0         if (@children_ids) {
348 0           my $child_tree = [];
349 0           foreach my $cid (@children_ids) {
350 0           $self->whole_tree($cid, $child_tree);
351             }
352 0           push @$tree, $child_tree;
353             }
354 0           $tree;
355             }
356              
357             =item _undirty($object)
358              
359             Return the object with all Dirty flags set to 0.
360              
361             =cut
362              
363             sub _undirty {
364 0     0     my($self, $obj) = @_;
365 0           $self->objectify_params($obj);
366 0           $obj->Dirty(0);
367 0           $obj->DirtyAttributes(0);
368 0           $obj->DirtyContent(0);
369 0           $self->replace_object($obj);
370             }
371              
372             =item is_locked($object_id)
373              
374             Return true if the object is locked by someone else.
375              
376             =cut
377              
378             sub is_locked {
379 0     0 1   my($self, $obj) = @_;
380 0           $self->objectify_params($obj);
381 0 0 0       return 0 if !defined $obj->LockedBy || $obj->LockedBy eq '';
382 0 0         return 0 if $obj->LockedBy eq $self->Root->CurrentUser;
383 0 0         if ($obj->LockType eq 'SessionLock') {
384 0 0         if ($self->Root->OnlineUserDB) {
385 0           my $r = $self->Root->OnlineUserDB->check_logged($obj->LockedBy);
386 0 0         if (!$r) {
387 0           $self->unlock($obj); # XXX -force => 1 ???
388             }
389 0           return $r;
390             } else {
391 0           return 0;
392             }
393             }
394 0 0         return 1 if ($obj->LockType eq 'PermanentLock'); # XXX probably check for existing user?
395 0           warn "Unknown lock type @{[ $obj->LockType ]}, assumed locked";
  0            
396 0           1;
397             }
398              
399             =item lock($object_id, -type => $lock_type)
400              
401             Lock the object C<$object_id>. Only single objects can be locked (no
402             folder hierarchies). Locking must be handled in the client by using
403             C. The C<$lock_type> may have the following values:
404              
405             =over 4
406              
407             =item SessionLock
408              
409             This lock should only be valid for this session. If the user closes
410             the session (either by a logout or by closing the browser window),
411             then the lock will be invalidated.
412              
413             =item PermanentLock
414              
415             This lock lasts over session ends.
416              
417             =back
418              
419             Return the object itself.
420              
421             Now, it should be checked programmatically whether the lock can be set
422             or not (by looking at the value is_locked). It is not clear what is
423             the right solution, because there are version control systems where
424             breaking locks is possible (RCS).
425              
426             =cut
427              
428             sub lock {
429 0     0 1   my($self, $obj_id, %args) = @_;
430 0 0         die "Lock -type is missing" if !$args{-type};
431 0 0         die "Valid Lock types are SessionLock and PermanentLock"
432             unless $args{-type} =~ /^(Session|Permanent)Lock$/;
433 0           $self->idify_params($obj_id);
434 0           my $obj = $self->get_object($obj_id);
435 0           $obj->LockedBy($self->Root->CurrentUser);
436 0           $obj->LockType($args{-type});
437 0           $obj->LockTime(epoch2isodate());
438 0           $self->replace_object($obj);
439             }
440              
441             =item unlock($object_id)
442              
443             Unlock the object with id C<$object_id>.
444              
445             Return the object itself.
446              
447             Now, it should be checked programmatically whether the lock can be
448             unset or not (by looking at the value is_locked). It is not clear what
449             is the right solution, because there are version control systems where
450             breaking locks is possible (RCS).
451              
452             =cut
453              
454             sub unlock {
455 0     0 1   my($self, $obj_id) = @_;
456 0           $self->idify_params($obj_id);
457 0           my $obj = $self->get_object($obj_id);
458 0           $obj->LockedBy(undef);
459 0           $obj->LockType(undef);
460 0           $obj->LockTime(undef);
461 0           $self->replace_object($obj);
462             }
463              
464             =item pathobjects($object_or_id [, $parent_obj])
465              
466             For the object or id C<$object_or_id>, the object path is returned.
467             This is similar to the C method, but returns a list of
468             objects instead of a pathname.
469              
470             If C<$parent_obj> is given as a object, then the returned pathname is
471             only a partial path starting from this parent object.
472              
473             =cut
474              
475             sub pathobjects {
476 0     0 1   my($self, $obj, $parent_obj) = @_;
477 0           $self->objectify_params($obj);
478 0 0 0       if (defined $parent_obj && $obj->Id eq $parent_obj->Id) {
479 0           return ();
480             }
481 0           my @parents = $self->parent_ids($obj->Id);
482 0 0         if (@parents) {
483 0           ($self->pathobjects($parents[0], $parent_obj), $obj);
484             } else {
485 0           ($obj);
486             }
487             }
488              
489             =item pathobjects_with_cache($object_or_id [, $parent_obj], $cache_hash_ref)
490              
491             As C, but also use a cache for a faster access.
492              
493             =cut
494              
495             sub pathobjects_with_cache {
496 0     0 1   my($self, $obj, $parent_obj, $cache) = @_;
497 0 0 0       if (!ref $obj && exists $cache->{$obj}) { # get by id
498 0           return @{ $cache->{$obj} };
  0            
499             }
500 0           $self->objectify_params($obj);
501 0 0         return () if !$obj;
502 0           my $objid = $obj->Id;
503 0 0         if (exists $cache->{$objid}) {
504 0           return @{ $cache->{$objid} };
  0            
505             }
506 0 0 0       if (defined $parent_obj && $objid eq $parent_obj->Id) {
507 0           $cache->{$obj->Id} = [];
508 0           return ();
509             }
510 0           my @parents = $self->parent_ids($objid);
511 0 0         if (@parents) {
512 0 0         if (exists $cache->{$parents[0]}) {
513 0           (@{ $cache->{$parents[0]} }, $obj);
  0            
514             } else {
515 0           my @parent_parents = $self->pathobjects_with_cache($parents[0], $parent_obj);
516 0           $cache->{$parents[0]} = [@parent_parents];
517 0           (@parent_parents, $obj);
518             }
519             } else {
520 0           ($obj);
521             }
522             }
523              
524             =item name_to_objid($name)
525              
526             Return the object id for the object containing the Attribute
527             C. If there is no such object, undef is returned. Note: This
528             method may or may not be efficient, depending whether there is an
529             index database (C) or not.
530              
531             =cut
532              
533             sub name_to_objid {
534 0     0 1   my($self, $name) = @_;
535 0           my $objid;
536 0 0         if ($self->Root->NameDB) {
537 0           $objid = $self->Root->NameDB->get_id($name);
538 0 0         return $objid if defined $objid;
539             }
540             # for backward compatibility (database without name.db)
541 0           eval {
542 0           local $SIG{__DIE__};
543             $self->walk($self->root_object->Id, sub {
544 0     0     my($id) = @_;
545 0           my $obj = $self->get_object($id);
546 0 0 0       if (defined $obj->Name && $obj->Name eq $name) {
547 0           $objid = $obj->Id;
548 0           die "Found";
549             }
550 0           });
551             };
552 0           $objid;
553             }
554              
555             1;
556              
557             __END__