File Coverage

blib/lib/Basset/NotificationCenter.pm
Criterion Covered Total %
statement 57 65 87.6
branch 20 40 50.0
condition 8 12 66.6
subroutine 9 10 90.0
pod 6 6 100.0
total 100 133 75.1


line stmt bran cond sub pod time code
1             package Basset::NotificationCenter;
2              
3             #Basset::NotificationCenter, copyright and (c) 2004, 2005, 2006 James A Thomason III
4             #Basset::NotificationCenter is distributed under the terms of the Perl Artistic License.
5              
6             =pod
7              
8             =head1 NAME
9              
10             Basset::NotificationCenter - used to notify other objects of interesting things
11              
12             =head1 AUTHOR
13              
14             Jim Thomason, jim@jimandkoka.com
15              
16             =head1 DESCRIPTION
17              
18             This concept is stolen lock stock and barrel from Objective-C (Apple's cocoa frameworks, specifically). Basically, the notification
19             center is a high level object that sits off to the side. Objects can register with it to pay attention to interesting things
20             that other objects do, and they can then act upon the interesting things.
21              
22             For example. Let's keep track of all times we see a weasel. First, we'll set up a logger (see Basset::Logger) to write to a log file.
23              
24             my $logger = Basset::Logger->new(
25             'handle' => '/tmp/weasels.log'
26             );
27              
28             Now, we register it as an observer
29              
30             Basset::NotificationCenter->addObserver(
31             'observer' => $logger,
32             'notification' => 'weasels',
33             'object' => 'all',
34             'method' => 'log'
35             );
36              
37             And we're done! Now we've registered our observer that will watch for "weasels" notifications posted by all objects, and when it
38             seems them, it will call its log method.
39              
40             So when a notification is posted:
41              
42             Basset::NotificationCenter->postNotification(
43             'object' => $henhouse,
44             'notification' => 'weasels',
45             'args' => ["Weasels in the hen house!"]
46             );
47              
48             That will look for all observers registered to watch for 'weasels' notifications (our logger, in this case) and call their methods.
49             Again, for our example, internally the notification center fires off:
50              
51             $logger->log("Weasels in the hen house!");
52              
53             Which logs the line of data to our /tmp/weasels.log file.
54              
55             You will B to put a types entry into your conf file for
56              
57             notificationcenter=Basset::NotificationCenter
58              
59             (or whatever center you're using)
60              
61             =cut
62              
63             $VERSION = '1.02';
64              
65 5     5   1291 use Scalar::Util qw(weaken isweak);
  5         15  
  5         1560  
66              
67 5     5   33 use Basset::Object;
  5         12  
  5         297  
68             our @ISA = Basset::Object->pkg_for_type('object');
69              
70 5     5   39 use strict;
  5         9  
  5         206  
71 5     5   45 use warnings;
  5         13  
  5         5610  
72              
73             =pod
74              
75             =head1 ATTRIBUTES
76              
77             =over
78              
79             =cut
80              
81             =pod
82              
83             =begin btest observers
84              
85             $test->ok(1, "testing is implied");
86              
87             =end btest
88              
89             =cut
90              
91             # the observers list is handled internally. It keeps track of the registered observers.
92             __PACKAGE__->add_attr('observers');
93              
94             =pod
95              
96             =item observation
97              
98             This is useful for debugging purposes. Set the notification center as an observer, and the
99             observation will contain the most recently postend notification.
100              
101              
102             Basset::NotificationCenter->addObserver(
103             'observer' => Basset::NotificationCenter->new,
104             'notification' => "YOUR NOTIFICATION,
105             'object' => 'all',
106             'method' => 'observation'
107             );
108              
109             =cut
110              
111             =pod
112              
113             =begin btest observation
114              
115             my $center = __PACKAGE__->new();
116             $test->ok($center, 'got default center');
117              
118             $test->is($center->addObserver('method' => 'observation', 'observer' => $center, 'notification' => 'foo'), 1, 'Added center-as-observer for foo from all');
119              
120             $test->is($center->postNotification('notification' => 'foo', 'object' => $center), 1, "Center posted foo notification");
121             my $note = $center->observation;
122             $test->is($note->{'object'}, $center, 'Notification object is center');
123             $test->is($note->{'notification'}, 'foo', 'Notification is foo');
124              
125             $test->is($center->removeAllObservers(), 1, 'cleaned up and removed observers');
126              
127             =end btest
128              
129             =cut
130              
131             __PACKAGE__->add_attr('observation');
132              
133             =pod
134              
135             =item loggers
136              
137             loggers should be specified in the conf file. Similar spec to the 'types' entry.
138              
139             loggers %= error=/tmp/error.log
140             loggers %= warnings=/tmp/warnings.log
141             loggers %= info=/tmp/info.log
142              
143             etc. Those conf file entries create loggers watching all objects for error, warnings, and info notifications, and the
144             log files to which they write.
145              
146             =cut
147              
148             =pod
149              
150             =begin btest loggers
151              
152             =end btest
153              
154             =cut
155              
156             __PACKAGE__->add_default_class_attr('loggers');
157              
158             =pod
159              
160             =back
161              
162             =cut
163              
164             =pod
165              
166             =begin btest init
167              
168             my $o = __PACKAGE__->new();
169             $test->ok($o, 'got object');
170             $test->is(ref($o->observers), 'HASH', 'observers is hashref');
171              
172             =end btest
173              
174             =cut
175              
176             sub init {
177             return shift->SUPER::init(
178 4     4 1 30 'observers' => {},
179             @_
180             );
181             }
182              
183             =pod
184              
185             =head1 METHODS
186              
187             =over
188              
189             =cut
190              
191             =pod
192              
193             =item new
194              
195             Basset::NotificationCenter is a singleton. Calling the constructor will return the single instance that can exist. All other methods may be
196             called as either an object or a class method.
197              
198             =cut
199              
200             =pod
201              
202             =begin btest center
203              
204             $test->is(__PACKAGE__->center, __PACKAGE__->new, 'new returns center singleton');
205              
206             =end btest
207              
208             =cut
209              
210             __PACKAGE__->add_class_attr('center');
211              
212             =pod
213              
214             =begin btest new
215              
216             =end btest
217              
218             =cut
219              
220             sub new {
221 149     149 1 220 my $class = shift;
222            
223             #bail out and do nothing if we can't access the singleton. That means we're trying to
224             #notify very very early in the compilation process.
225             #We don't generate an error, because we may end up in an infinite loop because error
226             #tries to post a notification. Remember - this is -very- early in the compilation process
227             #if this breaks.
228 149 50       921 return unless $class->can('center');
229            
230 149 100       703 if (my $center = $class->center) {
231 145         534 return $center;
232             }
233            
234 4         39 $class->center($class->SUPER::new());
235              
236 4         17 my $loggers = $class->loggers;
237 4         17 foreach my $note (keys %$loggers) {
238 0         0 my $log = $loggers->{$note};
239              
240 0         0 my $l = Basset::Object->factory(
241             'type' => 'logger',
242             'handle' => $log
243             );
244              
245 0 0       0 if (defined $l) {
246 0         0 $class->center->addObserver(
247             'notification' => $note,
248             'observer' => $l,
249             'method' => 'log'
250             );
251             }
252              
253             }
254            
255 4         21 return $class->center;
256             };
257              
258             =pod
259              
260             =item postNotification
261              
262             Basset::NotificationCenter->postNotification(
263             'notification' => 'weasels',
264             'object' => $henhouse,
265             'args' => ["Weasels in the hen house!"]
266             );
267              
268             postNotification (say it with me, now) posts a notification. It expects 2-3 arguments.
269              
270             object - required. The object posting the notification. May be a class.
271             notification - required. A string containing the notification being posted.
272             args - optional. Additional arguments in an arrayref to pass through to any observers.
273              
274             The observer receives a hashref containing the args passed into postNotification.
275              
276             =cut
277              
278             =pod
279              
280             =begin btest postNotification
281              
282             package Basset::Test::Testing::Basset::NotificationCenter::postNotification;
283             our @ISA = qw(Basset::Object);
284              
285             Basset::Test::Testing::Basset::NotificationCenter::postNotification->add_attr('observation');
286              
287             package __PACKAGE__;
288              
289             my $center = Basset::NotificationCenter->new;
290              
291             my $o = Basset::Test::Testing::Basset::NotificationCenter::postNotification->new();
292             $test->ok($o, "got object");
293              
294             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'foo'), 1, 'Added observer for foo from all');
295             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'bar', 'object' => $o), 1, 'Added observer for bar from self');
296              
297             my $args = [qw(a b c)];
298             $test->ok($args, "Got args");
299              
300             $test->is($center->postNotification('notification' => 'foo', 'object' => $center, 'args' => $args), 1, "Center posted foo notification");
301             my $note = $o->observation;
302             $test->is($note->{'object'}, $center, 'Notification object is center');
303             $test->is($note->{'notification'}, 'foo', 'Notification is foo');
304             $test->is($note->{'args'}, $args, 'args are correct');
305              
306             $test->is(__PACKAGE__->postNotification('notification' => 'foo', 'object' => $center, 'args' => $args), 1, "Center posted foo notification through package");
307             $note = $o->observation;
308             $test->is($note->{'object'}, $center, 'Notification object is center');
309             $test->is($note->{'notification'}, 'foo', 'Notification is foo');
310             $test->is($note->{'args'}, $args, 'args are correct');
311              
312             $test->is($center->postNotification('notification' => 'bar', 'object' => $center, 'args' => $args), 1, "Center posted bar notification");
313             $note = $o->observation;
314             $test->is($note->{'object'}, $center, 'Notification object is center (object ignores bar from center)');
315             $test->is($note->{'notification'}, 'foo', 'Notification is foo (object ignores bar from center)');
316             $test->is($note->{'args'}, $args, 'args are correct (object ignores bar from center)');
317              
318             $test->is($center->postNotification('notification' => 'bar', 'object' => $o, 'args' => $args), 1, "o posted bar notification");
319             $note = $o->observation;
320             $test->is($note->{'object'}, $o, 'Notification object is o');
321             $test->is($note->{'notification'}, 'bar', 'Notification is bar');
322             $test->is($note->{'args'}, $args, 'args are correct');
323              
324             $test->is($center->postNotification('notification' => 'bar', 'object' => $o), 1, "o posted bar notification w/no args");
325             $note = $o->observation;
326             $test->is($note->{'object'}, $o, 'Notification object is o');
327             $test->is($note->{'notification'}, 'bar', 'Notification is bar');
328             $test->is(scalar(@{$note->{'args'}}), 0, 'args are empty arrayref');
329              
330             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'cam', 'object' => $o), 1, 'Added observer for cam from self');
331             $test->is($center->postNotification('notification' => 'cam', 'object' => $o), 1, "o posted cam notification w/no args");
332             $test->is(__PACKAGE__->postNotification('notification' => 'cam', 'object' => $o), 1, "o posted cam notification w/no args via class");
333              
334             $test->is($center->removeAllObservers(), 1, 'cleaned up and removed observers');
335              
336             =end btest
337              
338             =cut
339              
340             sub postNotification {
341              
342 139     139 1 190 my $self = shift;
343 139 50       538 $self = ref $self ? $self : $self->new() or return;
    50          
344              
345 139         1544 my %args = @_;
346              
347 139 50       422 return $self->error("Cannot post notification w/o object", "BN-07") unless defined $args{'object'};
348 139 50       691 return $self->error("Cannot post notification w/o notification", "BN-08") unless defined $args{'notification'};
349            
350 139   50     424 $args{'args'} ||= [];
351            
352 139         514 my $observers = $self->observers();
353            
354 139         330 my $note = $observers->{$args{'notification'}};
355            
356 139 100       404 if (my $observableObjects = $observers->{$args{'notification'}}) {
357 70         243 foreach my $object (keys %$observableObjects) {
358              
359 72 100 66     633 if (defined $object && $object eq $args{'object'} || $object eq 'all') {
      100        
360 71         117 my $observers = $observableObjects->{$object};# || {};
361 71         472 foreach my $observerKey (keys %$observers) {
362 11         17 my $data = $observers->{$observerKey};
363 11         30 my ($observer, $method) = @$data{qw(observer method)};
364 11         75 $observer->$method(\%args);
365             }
366             }
367             }
368             };
369 139         556 return 1;
370             }
371              
372             =pod
373              
374             =item addObserver
375              
376             Basset::NotificationCenter->addObserver(
377             'observer' => $logger
378             'notification' => 'weasels',
379             'object' => 'all',
380             'method' => 'log'
381             );
382              
383             addObserver (say it with me, now) adds an observer. It expects 3-4 arguments.
384              
385             observer - required. The object observing the notification. May be a class.
386             notification - required. A string containing the notification to watch for.
387             method - required. The method to call when the notification is observed.
388             object - optional. If specified, then the observer will only watch for notifications posted by that object (or class).
389             otherwise, watches for all notifications of that type.
390              
391             =cut
392              
393             =pod
394              
395             =begin btest addObserver
396              
397             package Basset::Test::Testing::Basset::NotificationCenter::addObserver;
398             our @ISA = qw(Basset::Object);
399              
400             Basset::Test::Testing::Basset::NotificationCenter::addObserver->add_attr('observation');
401              
402             package __PACKAGE__;
403              
404             my $o = Basset::Test::Testing::Basset::NotificationCenter::addObserver->new();
405             $test->ok($o, "got object");
406              
407             my $center = __PACKAGE__->new();
408             $test->ok($center, 'got default center');
409              
410             $test->is(scalar($center->addObserver), undef, 'Could not add observer w/o method');
411             $test->is($center->errcode, 'BN-01', 'proper error code');
412             $test->is(scalar(__PACKAGE__->addObserver), undef, 'Could not add observer w/o method through package');
413             $test->is($center->errcode, 'BN-01', 'proper error code');
414             $test->is(scalar($center->addObserver('method' => 'observation')), undef, 'Could not add observer w/o observer');
415             $test->is($center->errcode, 'BN-02', 'proper error code');
416             $test->is(scalar($center->addObserver('method' => 'observation', 'observer' => $o)), undef, 'Could not add observer w/o notification');
417             $test->is($center->errcode, 'BN-03', 'proper error code');
418             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'foo'), 1, 'Added observer for foo from all');
419             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'bar', 'object' => $o), 1, 'Added observer for bar from self');
420              
421             my $args = [qw(a b c)];
422             $test->ok($args, "Got args");
423              
424             $test->is($center->postNotification('notification' => 'foo', 'object' => $center, 'args' => $args), 1, "Center posted foo notification");
425             my $note = $o->observation;
426             $test->is($note->{'object'}, $center, 'Notification object is center');
427             $test->is($note->{'notification'}, 'foo', 'Notification is foo');
428             $test->is($note->{'args'}, $args, 'args are correct');
429              
430             $test->is($center->postNotification('notification' => 'bar', 'object' => $center, 'args' => $args), 1, "Center posted bar notification");
431             $note = $o->observation;
432             $test->is($note->{'object'}, $center, 'Notification object is center (object ignores bar from center)');
433             $test->is($note->{'notification'}, 'foo', 'Notification is foo (object ignores bar from center)');
434             $test->is($note->{'args'}, $args, 'args are correct (object ignores bar from center)');
435              
436             $test->is($center->postNotification('notification' => 'bar', 'object' => $o, 'args' => $args), 1, "o posted bar notification");
437             $note = $o->observation;
438             $test->is($note->{'object'}, $o, 'Notification object is o');
439             $test->is($note->{'notification'}, 'bar', 'Notification is bar');
440             $test->is($note->{'args'}, $args, 'args are correct');
441              
442             $test->is($center->postNotification('notification' => 'bar', 'object' => $o), 1, "o posted bar notification w/no args");
443             $note = $o->observation;
444             $test->is($note->{'object'}, $o, 'Notification object is o');
445             $test->is($note->{'notification'}, 'bar', 'Notification is bar');
446             $test->is(scalar(@{$note->{'args'}}), 0, 'args are empty arrayref');
447              
448             $test->is($center->removeAllObservers(), 1, 'cleaned up and removed observers');
449              
450             =end btest
451              
452             =cut
453              
454             sub addObserver {
455 5     5 1 1277 my $self = shift;
456 5 50       29 $self = ref $self ? $self : $self->new() or return;
    50          
457            
458 5         36 my %init = @_;
459            
460 5 50       18 return $self->error("Cannot add observer w/o method", "BN-01") unless defined $init{'method'};
461 5 50       16 return $self->error("Cannot add observer w/o observer", "BN-02") unless defined $init{'observer'};
462 5 50       14 return $self->error("Cannot add observer w/o notification", "BN-03") unless defined $init{'notification'};
463 5   50     13 $init{'object'} ||= 'all';
464            
465 5         19 my $observers = $self->observers();
466            
467 5         46 $observers->{$init{'notification'}}->{$init{'object'}}->{$init{'observer'}} = \%init;
468            
469             #this is off for now, 'til I think of how to deal with the case of objects that exist
470             #only in the notification center, such as loggers
471             #
472             #we don't want the notification center to keep observer objects around by mistake.
473             #weaken($init{'observer'}) if ref $init{'observer'};
474            
475 5         26 return 1;
476             };
477              
478             =pod
479              
480             =item removeObserver
481              
482             Basset::NotificationCenter->removeObserver(
483             'observer' => $logger
484             'notification' => 'weasels',
485             );
486              
487             removeObserver (say it with me, now) removes an observer. It expects 2 arguments.
488              
489             observer - required. The object observing the notification. May be a class.
490             notification - required. A string containing the notification to watch for.
491              
492             Behave yourself and properly manage your memory. Remove observers when you're no longer using them. This is especially important
493             in a mod_perl environment.
494              
495             =cut
496              
497             =pod
498              
499             =begin btest removeObserver
500              
501             package Basset::Test::Testing::Basset::NotificationCenter::removeObserver;
502             our @ISA = qw(Basset::Object);
503              
504             Basset::Test::Testing::Basset::NotificationCenter::removeObserver->add_attr('observation');
505              
506             package __PACKAGE__;
507              
508             my $o = Basset::Test::Testing::Basset::NotificationCenter::removeObserver->new();
509             $test->ok($o, "got object");
510              
511             my $center = __PACKAGE__->new();
512             $test->ok($center, 'got default center');
513              
514             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'foo'), 1, 'Added observer for foo from all');
515             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'bar', 'object' => $o), 1, 'Added observer for bar from self');
516              
517             $test->is(scalar($center->removeObserver), undef, 'Could not remove observer w/o observer');
518             $test->is($center->errcode, 'BN-05', 'proper error code');
519             $test->is(scalar(__PACKAGE__->removeObserver), undef, 'Could not remove observer w/o observer through package');
520             $test->is($center->errcode, 'BN-05', 'proper error code');
521             $test->is(scalar($center->removeObserver('observer' => $o)), undef, 'Could not remove observer w/o notification');
522             $test->is($center->errcode, 'BN-06', 'proper error code');
523              
524             $test->is($center->removeObserver('observer' => $o, 'notification' => 'foo'), 1, 'removed foo notification');
525             $test->is(scalar($o->observation(undef)), undef, 'wiped out any previous notifications');
526             $test->is($center->postNotification('notification' => 'foo', 'object' => $o), 1, "o posted foo notification");
527             my $note = $o->observation;
528             $test->is($note, undef, "No notification received");
529              
530             $test->is($center->postNotification('notification' => 'bar', 'object' => $o), 1, "o posted bar notification w/no args");
531             $note = $o->observation;
532             $test->is($note->{'object'}, $o, 'Notification object is o');
533             $test->is($note->{'notification'}, 'bar', 'Notification is bar');
534             $test->is(scalar(@{$note->{'args'}}), 0, 'args are empty arrayref');
535              
536             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'foo'), 1, 'Re-Added observer for foo from all');
537              
538             $test->is($center->removeObserver('observer' => $o, 'notification' => 'bar'), 1, 'removed bar notification for all');
539              
540             $test->is(scalar($o->observation(undef)), undef, 'wiped out any previous notifications');
541              
542             $test->is($center->postNotification('notification' => 'bar', 'object' => $o), 1, "o posted bar notification");
543             $note = $o->observation;
544             $test->is($note->{'object'}, $o, 'Notification object is o');
545             $test->is($note->{'notification'}, 'bar', 'Notification is bar');
546             $test->is(scalar(@{$note->{'args'}}), 0, 'args are empty arrayref');
547              
548             $test->is($center->removeObserver('observer' => $o, 'notification' => 'bar', 'object' => $o), 1, 'removed bar notification for $o');
549              
550             $test->is(scalar($o->observation(undef)), undef, 'wiped out any previous notifications');
551              
552             $test->is($center->postNotification('notification' => 'bar', 'object' => $o), 1, "o posted bar notification");
553             $note = $o->observation;
554              
555             $test->is($note, undef, "No notification received");
556              
557             $test->is($center->postNotification('notification' => 'foo', 'object' => $center), 1, "Center posted foo notification");
558             $note = $o->observation;
559             $test->is($note->{'object'}, $center, 'Notification object is center');
560             $test->is($note->{'notification'}, 'foo', 'Notification is foo');
561              
562             $test->is($center->removeAllObservers(), 1, 'cleaned up and removed observers');
563              
564             =end btest
565              
566             =cut
567              
568             sub removeObserver {
569 5     5 1 3676 my $self = shift;
570 5 50       28 $self = ref $self ? $self : $self->new() or return;
    50          
571            
572 5         25 my %init = @_;
573            
574 5 50       17 return $self->error("Cannot remove observer w/o observer", "BN-05") unless defined $init{'observer'};
575 5 50       16 return $self->error("Cannot remove observer w/o notification", "BN-06") unless defined $init{'notification'};
576 5   50     28 $init{'object'} ||= 'all';
577            
578 5         20 my $observers = $self->observers();
579            
580 5         30 delete $observers->{$init{'notification'}}->{$init{'object'}}->{$init{'observer'}};
581            
582 5         26 return 1;
583            
584             };
585            
586             =pod
587              
588             =item removeAllObservers
589              
590             Sometimes, though, it's easier to just nuke all the existing observers. The end of execution in a mod_perl process, for instance. You don't
591             need to care what observers are still around or what they're doing. You just want them to go away. So you can remove them all.
592              
593             Basset::NotificationCenter->removeAllObservers();
594              
595             =cut
596              
597             =pod
598              
599             =begin btest removeAllObservers
600              
601             package Basset::Test::Testing::Basset::NotificationCenter::removeAllObservers;
602             our @ISA = qw(Basset::Object);
603              
604             Basset::Test::Testing::Basset::NotificationCenter::removeAllObservers->add_attr('observation');
605              
606             package __PACKAGE__;
607              
608             my $o = Basset::Test::Testing::Basset::NotificationCenter::removeAllObservers->new();
609             $test->ok($o, "got object");
610              
611             my $center = __PACKAGE__->new();
612             $test->ok($center, 'got default center');
613              
614             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'foo'), 1, 'Added observer for foo from all');
615             $test->is($center->addObserver('method' => 'observation', 'observer' => $o, 'notification' => 'bar', 'object' => $o), 1, 'Added observer for bar from self');
616              
617             my $observers = $center->observers;
618              
619             $test->is(scalar(keys %{$center->observers}), 2, 'two observers');
620             $test->is($center->removeAllObservers, 1, 'removed all observers');
621             $test->is(scalar(keys %{$center->observers}), 0, 'no more observers');
622              
623             $center->observers($observers);
624              
625             =end btest
626              
627             =cut
628              
629             sub removeAllObservers {
630 0     0 1   my $self = shift;
631 0 0         $self = ref $self ? $self : $self->new() or return;
    0          
632            
633 0           $self->observers({});
634            
635 0           return 1;
636             };
637              
638             1;
639              
640             =pod
641              
642             =back
643              
644             =cut