File Coverage

blib/lib/Message/SmartMerge.pm
Criterion Covered Total %
statement 129 135 95.5
branch 45 64 70.3
condition 19 37 51.3
subroutine 16 16 100.0
pod 7 7 100.0
total 216 259 83.4


line stmt bran cond sub pod time code
1             package Message::SmartMerge;
2             $Message::SmartMerge::VERSION = '1.161240';
3 8     8   170472 use 5.006;
  8         19  
4 8     8   28 use strict;
  8         10  
  8         150  
5 8     8   26 use warnings FATAL => 'all';
  8         11  
  8         253  
6 8     8   460 use Message::Match qw(mmatch);
  8         1211  
  8         323  
7 8     8   429 use Message::Transform qw(mtransform);
  8         306  
  8         7619  
8              
9             =head1 NAME
10              
11             Message::SmartMerge - Enforce downstream transformations on message streams
12              
13             =cut
14              
15             =head1 SYNOPSIS
16              
17             use Message::SmartMerge;
18              
19             my $merge = Message::SmartMerge->new();
20             $merge->config({
21             merge_instance => 'instance',
22             });
23              
24             $merge->message({
25             instance => 'i1',
26             x => 'y',
27             this => 'whatever',
28             });
29             #no merges, so pass through:
30             #emit sends { instance => 'i1', x => 'y', this => 'whatever' }
31            
32             $merge->add_merge({
33             merge_id => 'm1',
34             match => {x => 'y'},
35             transform => {this => 'that'},
36             });
37             #so we've already passed through a message instance i1, and this new
38             #merge matches that, so the module will send a transformed message:
39             { instance => 'i1',
40             x => 'y',
41             this => 'that',
42             }
43              
44             #Now send another message through:
45             $merge->message({
46             instance => 'i1',
47             x => 'y',
48             this => 'not that',
49             something => 'else',
50             });
51             #merge matches x => 'y', so transforms this => 'that':
52             #emit sends:
53             { instance => 'i1',
54             x => 'y',
55             this => 'that',
56             something => 'else'
57             }
58              
59             $merge->remove_merge('m1');
60             #even though we didn't send a message in, removing a merge will trigger
61             #an emit to reflect that a change has occurred, specifically that the
62             #previously activated transform is no longer in force. It sends the
63             #last message received, without the transform
64             #emit sends:
65             { instance => 'i1',
66             x => 'y',
67             this => 'not that',
68             something => 'else'
69             }
70              
71             #Here's a way the message stream can clear a merge:
72             $merge->add_merge({
73             merge_id => 'm2',
74             match => {
75             x => 'y',
76             },
77             transform => {
78             foo => 'bar',
79             },
80             toggle_fields => ['something'],
81             });
82              
83             #Since m2 also matches x => 'y', we emit:
84             { instance => 'i1',
85             x => 'y',
86             this => 'not that',
87             something => 'else',
88             foo => 'bar',
89             }
90              
91             $merge->message({
92             instance => 'i1',
93             x => 'y',
94             foo => 'not bar',
95             something => 'else',
96             another => 'thing',
97             });
98             #the value of the single defined toggle field ('something') did not
99             #change from the first value we saw in it ('else'). So m2 stands:
100             { instance => 'i1',
101             x => 'y',
102             something => 'else',
103             foo => 'bar',
104             another => 'thing',
105             }
106             #even though we passed 'not bar' in with foo, it was transformed to 'bar'
107              
108             #Now let's hit the toggle:
109             $merge->message({
110             instance => 'i1',
111             x => 'y',
112             foo => 'not bar',
113             something => 'other',
114             another => 'thing',
115             });
116             #this will 'permanently' remove the merge m2 for i1; the message passes
117             #through untransformed:
118             { instance => 'i1',
119             x => 'y',
120             foo => 'not bar',
121             something => 'other',
122             another => 'thing',
123             }
124              
125             #Here's another way the message stream can clear a merge:
126             $merge->add_merge({
127             merge_id => 'm3',
128             match => {
129             i => 'j',
130             },
131             transform => {
132             a => 'b',
133             },
134             remove_match => {
135             remove => 'match',
136             },
137             });
138             #This causes nothing to emit, because there are no instances that match
139             #i => 'j'
140              
141             $merge->message({
142             instance => 'i2',
143             x => 'y',
144             i => 'j',
145             foo => 'not bar',
146             a => 'not b',
147             something => 'here',
148             });
149             #this is fun because it matches both m2 and m3. it would have matched
150             #m1 had we not removed it
151             #i2 has never been seen before, and m2 is a toggle. The toggle
152             #deallocates itself for an instance if the toggle field changes
153             #from the previous to the current message. Since there was no
154             #previous message for i2, the toggle merge deallocates itself for i2
155             #before it can take any action.
156             { instance => 'i2',
157             x => 'y',
158             i => 'j',
159             foo => 'not bar',
160             something => 'here',
161             a => 'b', #rather than 'not b'
162             }
163              
164             #and now to deallocate m3:
165             $merge->message({
166             instance => 'i2',
167             x => 'y',
168             i => 'j',
169             a => 'not b',
170             remove => 'match',
171             });
172             #which emits:
173             { instance => 'i2',
174             x => 'y',
175             i => 'j',
176             a => 'not b', #no longer transformed
177             remove => 'match',
178             }
179              
180             =head1 DESCRIPTION
181              
182             In message based programming, we think in terms of streams of messages
183             flowing from one way-point to another. Each way-point does only one thing
184             to messages flowing through it, independent from various other way-points.
185             The contract between these are required fields in the messages.
186              
187             This module is designed to modify the state of a stream of messages in a
188             powerful and configurable way.
189              
190             Conceptually, it will enforce certain transformations on certain message
191             streams. If the nature of the transformation changes, (for instance, if
192             it expires, or is deallocated some other way), the module will send a
193             'corrective' message.
194              
195             We call these configurations 'merges'. Part of a merge is a transformation.
196              
197             For example, when a new merge is configured, all of the matching message
198             instances will be re-sent with the new transform in force. And when
199             the merge is removed or expires, all of the matching message instances
200             are re-sent with their last received values. This effectively causes the
201             downstream receiver to be aware of stateful changes, but in a fully
202             message-oriented fashion.
203              
204             Merges can be added and removed explicitly with add_merge and remove_merge.
205             They can also expire, with expire and expire_at.
206              
207             More interestingly, merges can be deallocated for a given message stream
208             using one or two configurations: remove_match and toggle_fields.
209              
210             remove_match is simplest: if a message instance is under the influence of a
211             given merge that contains a remove_match config, and that message matches
212             the remove_match, then the merge is, for that instance, deallocated. The
213             message passes through that merge unchanged.
214              
215             toggle_fields is more tricky: it is an array of fields in the message to
216             consider. The toggle_fields configured merge will continue to be in force
217             as long as the value of all of the fields in toggle_fields is un-changed.
218             As soon as any of those values changes, the merge is, for that instance,
219             deallocated.
220              
221             This is pretty abstract stuff; more concrete examples will be forthcoming
222             in subsequent releases.
223              
224             =head1 SUBROUTINES/METHODS
225              
226             =head2 new
227              
228             my $merge = Message::SmartMerge->new(state => $previous_state);
229              
230             =over 4
231              
232             =item * state (optional)
233              
234             The hashref returned by a previous invocation of C<get_state>
235              
236             =back
237              
238             =cut
239              
240             sub new {
241 8     8 1 71 my $class = shift;
242 8         14 my $self = {};
243 8 50       24 die "Message::SmartMerge::new: even number of argument required\n"
244             if scalar @_ % 2;
245 8         18 my %args = @_;
246 8         10 bless ($self, $class);
247              
248             $self->{keep_only} = $args{keep_only}
249 8 50       20 if $args{keep_only};
250 8   100     66 $self->{merges} = $args{state}->{merges} || {};
251 8   100     34 $self->{instances} = $args{state}->{instances} || {};
252             $self->config($args{state}->{config})
253             if $args{state} and
254 8 100 33     32 $args{state}->{config};
255 8         39 return $self;
256             }
257              
258              
259             =head2 get_state
260              
261             my $state_to_save = $merge->get_state();
262              
263             This method takes no arguments; it returns a hashref to all of the data
264             necessary to re-create the current behaviour of this library.
265              
266             Simply put, before your process exits, gather the return value of
267             get_state, and save it somewhere. When your process comes up, take that
268             information and pass it into the state key in the constructor. The library
269             will continue functioning as before.
270              
271             =cut
272             sub get_state {
273 1     1 1 3 my $self = shift;
274             return {
275             merges => $self->{merges},
276             instances => $self->{instances},
277             config => $self->{config},
278 1         8 };
279             }
280              
281             =head2 emit
282              
283             $merge->emit(%args)
284              
285             This method is designed to be over-ridden; the default implementation simply
286             adds the passed message to the package global
287             @Message::SmartMerge::return_messages and returns all of the arguments
288              
289             =over 4
290              
291             =item * message
292              
293             The message being sent out, which is a HASHref.
294              
295             =item * matching_merge_ids
296              
297             A HASHref whose keys are the merge IDs that were applied, and values are 1.
298              
299             =item * other: things (TODO)
300              
301             =back
302              
303             =cut
304             our @return_messages = ();
305             sub emit {
306 62     62 1 60 my $self = shift;
307 62         126 my %args = @_;
308 62         83 push @return_messages, $args{message};
309 62         245 return \%args;
310             }
311              
312             =head2 config
313              
314             $merge->config({
315             merge_instance => 'instance_key',
316             });
317              
318             =over 4
319              
320             =item * config_def (positional, required)
321              
322             HASHref of configuration
323              
324             =over 4
325              
326             =item * merge_instance (required)
327              
328             This is a scalar must exist as a key to every incoming message. The value
329             of this key must also be a scalar, and represent the 'instance' of a message
330             stream. That is, all messages of the same instance are considered a unified
331             stream.
332              
333             =back
334              
335             =back
336              
337             =cut
338             sub config {
339 8     8 1 12 my $self = shift;
340 8 50       26 my $new_config = shift or die "Message::SmartMerge::config: at least one argument is required\n";
341             die "Message::SmartMerge::config: required config attribute 'merge_instance' must be a scalar\n"
342             if not $new_config->{merge_instance} or
343 8 50 33     55 ref $new_config->{merge_instance};
344 8         16 $self->{config} = $new_config;
345 8         22 return $new_config;
346             }
347              
348             sub _expire_merges {
349 62     62   49 my $self = shift;
350 62         82 my $ts = time;
351 62         51 foreach my $merge_id (keys %{$self->{merges}}) {
  62         165  
352 65         68 my $merge = $self->{merges}->{$merge_id};
353 65 100       138 if($merge->{expire} < $ts) {
354 3         14 $self->remove_merge($merge_id);
355             }
356             }
357             }
358              
359             sub _get_only {
360 62     62   54 my $self = shift;
361 62         46 my $message = shift;
362 62 50 33     199 if(not $self->{config} or not $self->{config}->{keep_only}) {
363 62         118 return $message;
364             }
365 0         0 my $ret = {};
366 0         0 $ret->{$_} = $message->{$_} for @{$self->{config}->{keep_only}};
  0         0  
367 0         0 return $ret;
368             }
369              
370             =head2 add_merge
371              
372             $merge->add_merge({
373             merge_id => 'm1',
374             match => {x => 'y'},
375             transform => {this => 'to that'},
376             expire => 120, #expire in two minutes
377             expire_at => 1465173300, #expire in June 2016 (TODO)
378             });
379              
380             =over 4
381              
382             =item * merge_def (first positional, required)
383              
384             =over 4
385              
386             =item * merge_id (required)
387              
388             Unique scalar identifying this merge
389              
390             =item * match (required)
391              
392             Message::Match object (HASHref); defines messages this merge applies to
393              
394             =item * transform (required)
395              
396             Message::Transform object (HASHref); what changes to make
397             NOTE: considering not making transform required
398              
399             =item * expire (optional)
400              
401             How many seconds (integer) before this merge expires
402              
403             =item * expire_at (optional) (TODO)
404              
405             Epoch time (integer) this merge will expire
406              
407             =back
408              
409             =back
410              
411             =head3 exceptions
412              
413             =over 4
414              
415             =item * must have at least one argument, a HASH reference
416              
417             =item * passed merge must have a scalar merge_id
418              
419             =item * passed merge_id '$merge_id' is already defined
420              
421             =back
422              
423             =cut
424             sub add_merge {
425 12 50   12 1 1489 my $self = shift or die "Message::SmartMerge::add_merge: must be called as a method\n";
426 12         14 my $merge = shift;
427 12 50 33     102 die "Message::SmartMerge::add_merge: must have at least one argument, a HASH reference\n"
      33        
428             if not $merge or
429             not ref $merge or
430             ref $merge ne 'HASH';
431 12 50       30 die "Message::SmartMerge::add_merge: even number of argument required\n"
432             if scalar @_ % 2;
433 12         17 my %args = @_;
434              
435 12         16 my $merge_id = $merge->{merge_id};
436 12 50 33     47 die "Message::SmartMerge::add_merge: passed merge must have a scalar merge_id\n"
437             if not $merge_id or
438             ref $merge_id;
439             die "Message::SmartMerge::add_merge: passed merge_id '$merge_id' is already defined\n"
440 12 50       25 if $self->{merges}->{$merge_id};
441 12 100       26 if($merge->{expire}) {
442 3         6 $merge->{expire} = time + $merge->{expire};
443             } else {
444 9         15 $merge->{expire} = 2147483647; #2^31 - 1 job security!
445             }
446 12         21 $self->{merges}->{$merge_id} = $merge;
447             #1. iterate through all of the message instances
448             #2. for each one that matches the new merge:
449             # a. n/a
450             # b. make a note of the message instance
451             #3. for each of the noted message instances,
452             # a. run the transforms
453             # b. emit the message
454 12         17 my @matched_instances = ();
455 12         14 foreach my $instance_name (sort keys %{$self->{instances}}) {
  12         62  
456 15         17 my $instance = $self->{instances}->{$instance_name};
457 15 100       45 next unless mmatch $instance->{message}, $merge->{match};
458              
459             #this instance matches the new merge
460 12         319 push @matched_instances, $instance_name;
461             }
462              
463             #for section 3 above, can I not simply call $self->message() on all of
464             #the matched messages?
465 12         86 $self->message($self->{instances}->{$_}->{message}) for @matched_instances;
466 12         40 return $merge;
467             }
468              
469             =head2 message
470              
471             $merge->message({
472             instance_key => 'instance1',
473             x => 'y',
474             });
475              
476             Coupled with the above defined merge, this message method will call the emit
477             method thusly: (Assuming it's still before the merge expired)
478              
479             ( message => {
480             instance_key => 'instance1',
481             x => 'y',
482             this => 'to that',
483             },
484             matching_merge_ids => {
485             m1 => 1,
486             },
487             )
488              
489             =over 4
490              
491             =item * message (first positional, required)
492              
493             =back
494              
495             =head3 exceptions
496              
497             =over 4
498              
499             =item * must have at least one argument, a HASH reference
500              
501             =item * passed message did not have instance field
502              
503             =back
504              
505             =cut
506             sub message {
507 62 50   62 1 5571 my $self = shift or die "Message::SmartMerge::message: must be called as a method\n";
508 62         57 my $message = shift;
509 62 50 33     380 die "Message::SmartMerge::message: must have at least one argument, a HASH reference\n"
      33        
510             if not $message or
511             not ref $message or
512             ref $message ne 'HASH';
513 62 50       219 die "Message::SmartMerge::message: even number of argument required\n"
514             if scalar @_ % 2;
515 62         77 my %args = @_;
516              
517 62         154 $self->_expire_merges(); #hideously inefficient; have this run at most
518             #once per second TODO
519              
520             #1. find message instance
521             #2. gather all of the merges that match
522             # a. identify any merges that should be cleared for this instance
523             # aa. check toggle_fields
524             # bb. check remove_match
525             #3. eliminate all of the merges that have toggled off or have cleared
526             #4. run the transforms
527             #5. emit the message
528 62         71 my $config = $self->{config};
529             my $instance_name = $message->{$config->{merge_instance}}
530 62 50       131 or die "Message::SmartMerge::message: passed message did not have instance field '$config->{merge_instance}'\n";
531              
532 62         55 my $instances = $self->{instances};
533 62         47 my $previous_message;
534 62 100       125 if(not $instances->{$instance_name}) {
535 15         32 $instances->{$instance_name} = {
536             cleared_merges => {},
537             initial_ts => time,
538             message => $self->_get_only($message),
539             };
540             } else {
541 47         49 $previous_message = $instances->{$instance_name}->{message};
542 47         69 $instances->{$instance_name}->{message} = $self->_get_only($message);
543             }
544 62         68 my $instance = $instances->{$instance_name};
545 62         59 my $matching_merge_ids = {};
546 62         54 foreach my $merge_id (keys %{$self->{merges}}) {
  62         106  
547 62 100       98 next if $instance->{cleared_merges}->{$merge_id};
548 53         50 my $merge = $self->{merges}->{$merge_id};
549 53 100       97 if(mmatch $message, $merge->{match}) {
550 47         912 my $include = 1;
551             #so we have a matching merge that hasn't been previously
552             #eliminated
553             #figure out if it needs to be eliminated
554 47 100       74 if($merge->{toggle_fields}) { #section 2.a.aa
555 9         5 foreach my $toggle_field (@{$merge->{toggle_fields}}) {
  9         16  
556 9         5 my $toggle_field_value = $message->{$toggle_field};
557 9         10 my $previous_toggle_field_value = $previous_message->{$toggle_field};
558 9 100 100     49 if( not defined $toggle_field_value or
      100        
559             not defined $previous_toggle_field_value or
560             $toggle_field_value ne $previous_toggle_field_value) {
561             #toggles are different; remove this merge
562 4         4 $include = 0;
563 4         9 $instance->{cleared_merges}->{$merge_id} = 1;
564             }
565             }
566             }
567              
568 47 100       65 if($merge->{remove_match}) { #section 2.a.bb
569 3 100       5 if(mmatch $message, $merge->{remove_match}) {
570 1         17 $include = 0;
571 1         2 $instance->{cleared_merges}->{$merge_id} = 1;
572             }
573             }
574              
575 47 100       130 $matching_merge_ids->{$merge_id} = 1 if $include;
576             }
577             }
578             #at this point, $matching_merge_ids contains all of the merges we need
579              
580 62         160 my $emit_message = _fast_clone($message);
581             #4b: transform any remaining merges
582 62         61 foreach my $merge_id (keys %{$matching_merge_ids}) {
  62         128  
583 42         116 my $merge = $self->{merges}->{$merge_id};
584 42         92 mtransform $emit_message, $merge->{transform};
585             }
586              
587 62         497 $self->emit(message => $emit_message, matching_merge_ids => $matching_merge_ids);
588             }
589              
590             sub _fast_clone {
591 8     8   4363 use Storable;
  8         17458  
  8         1983  
592 62     62   55 my $thing = shift;
593 62         1828 return Storable::dclone $thing;
594             }
595              
596              
597              
598             =head2 remove_merge
599              
600             $merge->remove_merge('m1');
601              
602             =over 4
603              
604             =item * merge_id (first positional, required)
605              
606             The merge_id to be removed.
607              
608             =back
609              
610             =head3 exceptions
611              
612             =over 4
613              
614             =item * passed merge_id does not reference an existing merge
615              
616             =item * must have at least one argument, a scalar
617              
618             =back
619              
620             =cut
621             sub remove_merge {
622 7 50   7 1 475 my $self = shift or die "Message::SmartMerge::remove_merge: must be called as a method\n";
623 7         13 my $merge_id = shift;
624 7 50 33     41 die "Message::SmartMerge::remove_merge: must have at least one argument, a scalar\n"
625             if not $merge_id or
626             ref $merge_id;
627 7 50       24 die "Message::SmartMerge::remove_merge: even number of argument required\n"
628             if scalar @_ % 2;
629 7         12 my %args = @_;
630              
631             die "Message::SmartMerge::remove_merge: passed merge_id does not reference an existing merge\n"
632 7 50       26 unless $self->{merges}->{$merge_id};
633             #should be about the same as add_merge, but 'in reverse'
634             #1. iterate through all of the message instances
635             #2. for each one that matches the to be deleted merge:
636             # a. skip and remove cleared_merges if cleared_merges matches this merge
637             # b. make a note of the message instance
638             #3. for each of the marked message instances,
639             # a. run the transforms
640             # b. emit the message
641 7         12 my $merge = $self->{merges}->{$merge_id};
642 7         14 my @matched_instances = ();
643 7         11 foreach my $instance_name (sort keys %{$self->{instances}}) {
  7         40  
644 17         52 my $instance = $self->{instances}->{$instance_name};
645 17 50       39 if($instance->{cleared_merges}->{$merge_id}) {
646 0         0 delete $instance->{cleared_merges}->{$merge_id};
647 0         0 next;
648             }
649 17 100       52 next unless mmatch $instance->{message}, $merge->{match};
650              
651             #this instance matches the new merge
652 9         297 push @matched_instances, $instance_name;
653             }
654 7         89 delete $self->{merges}->{$merge_id};
655 7         46 $self->message($self->{instances}->{$_}->{message}) for @matched_instances;
656 7         30 return $merge;
657             }
658              
659             =head1 AUTHOR
660              
661             Dana M. Diederich, <diederich@gmail.com>
662              
663             =head1 BUGS
664              
665             Please report any bugs or feature requests to C<bug-message-smartmerge at rt.cpan.org>, or through
666             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Message-SmartMerge>. I will be notified, and then you'll
667             automatically be notified of progress on your bug as I make changes.
668              
669              
670             =head1 SEE ALSO
671              
672             http://c2.com/cgi/wiki?AlanKayOnMessaging
673             http://spin.atomicobject.com/2012/11/15/message-oriented-programming/
674              
675              
676              
677             =head1 SUPPORT
678              
679             You can find documentation for this module with the perldoc command.
680              
681             perldoc Message::SmartMerge
682              
683              
684             You can also look for information at:
685              
686             =over 4
687              
688             =item * Report bugs and feature requests here
689              
690             L<https://github.com/dana/perl-Message-SmartMerge/issues>
691              
692             =item * AnnoCPAN: Annotated CPAN documentation
693              
694             L<http://annocpan.org/dist/Message-SmartMerge>
695              
696             =item * CPAN Ratings
697              
698             L<http://cpanratings.perl.org/d/Message-SmartMerge>
699              
700             =item * Search CPAN
701              
702             L<https://metacpan.org/module/Message::SmartMerge>
703              
704             =back
705              
706              
707             =head1 ACKNOWLEDGEMENTS
708              
709              
710             =head1 LICENSE AND COPYRIGHT
711              
712             Copyright 2013 Dana M. Diederich.
713              
714             This program is free software; you can redistribute it and/or modify it
715             under the terms of the the Artistic License (2.0). You may obtain a
716             copy of the full license at:
717              
718             L<http://www.perlfoundation.org/artistic_license_2_0>
719              
720             Any use, modification, and distribution of the Standard or Modified
721             Versions is governed by this Artistic License. By using, modifying or
722             distributing the Package, you accept this license. Do not use, modify,
723             or distribute the Package, if you do not accept this license.
724              
725             If your Modified Version has been derived from a Modified Version made
726             by someone other than you, you are nevertheless required to ensure that
727             your Modified Version complies with the requirements of this license.
728              
729             This license does not grant you the right to use any trademark, service
730             mark, tradename, or logo of the Copyright Holder.
731              
732             This license includes the non-exclusive, worldwide, free-of-charge
733             patent license to make, have made, use, offer to sell, sell, import and
734             otherwise transfer the Package with respect to any patent claims
735             licensable by the Copyright Holder that are necessarily infringed by the
736             Package. If you institute patent litigation (including a cross-claim or
737             counterclaim) against any party alleging that the Package constitutes
738             direct or contributory patent infringement, then this Artistic License
739             to you shall terminate on the date that such litigation is filed.
740              
741             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
742             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
743             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
744             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
745             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
746             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
747             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
748             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
749              
750              
751             =cut
752              
753             1; # End of Message::SmartMerge
754              
755             __END__
756              
757             Notes:
758              
759             Algo: keep a list of all of the instances.
760             When a message arrives,
761              
762              
763              
764              
765              
766              
767              
768             ... other notes...
769              
770             we need to be able to send a message when there's any expiration.
771