File Coverage

blib/lib/Object/Recorder.pm
Criterion Covered Total %
statement 12 40 30.0
branch 0 10 0.0
condition 0 6 0.0
subroutine 4 9 44.4
pod 4 4 100.0
total 20 69 28.9


line stmt bran cond sub pod time code
1             package Object::Recorder;
2            
3 1     1   27062 use warnings;
  1         2  
  1         27  
4 1     1   5 use strict;
  1         2  
  1         28  
5            
6 1     1   1090 use Memoize;
  1         2614  
  1         48  
7 1     1   461 use Object::Recorder::Storage;
  1         2  
  1         535  
8            
9             =head1 NAME
10            
11             Object::Recorder - Records method calls into a serializable data structure
12            
13             =cut
14            
15             our $VERSION = '0.01';
16            
17             # this is a debug switch
18             # if true will call the underlying objects directly instead of recording
19             our $BYPASS_STORAGE = 0;
20            
21             =head1 SYNOPSIS
22            
23             This module makes it possible to record method calls issued to a set of objects
24             inti a serializable container which can later be replayed, perfoming the actual
25             method calls.
26            
27             use Object::Recorder;
28            
29             # start recording method calls to an instance of My::Object
30             # will build the object by calling My::Object->new( @params )
31             my @params = ('constructor', 'args');
32             my $obj = Object::Recorder->record(
33             'My::Object',
34             new => @params
35             );
36            
37             my @args = (1, 2, 'whatever');
38             $obj->some_method_call(@args);
39            
40             # this object will be blessed directly without calling any constructors
41             my $another_obj = Object::Recorder->record('Another::Object');
42            
43             # $another_object will only be created when $obj is replayed
44             $obj->another_method_call($another_object);
45            
46             # it's ok to have return values (currently, only 1 is supported)
47             my $return = $another_object->return_something();
48            
49             # this will DWIM
50             $return->call_method_on_returned_value();
51            
52             And then, somewhere else:
53            
54             $obj->replay();
55            
56             In this case, replaying will perform these steps:
57            
58             my $obj1 = My::Object->new('constructor', 'args');
59             $obj1->some_method_call(1, 2, 'whatever');
60            
61             my $obj2 = bless {}, 'Another::Object';
62             $obj1->another_method_call($obj2);
63            
64             my $ret = $obj2->return_something();
65             $ret->call_method_on_returned_value();
66            
67             This can be useful for several reasons. For instance, it could be used in the
68             creation of task objects which would then be processed by a cluster of worker
69             servers, without the need to update the worker code for each new type of task.
70            
71             It seems that this feature could also be useful in some sort of caching scheme
72             besides being useful in distributed systems in general.
73            
74             =head1 CLASS METHODS
75            
76             =head2 record( $class_name, [ $constructor, @args ]
77            
78             This method starts the recording process and returns an object which can be
79             used as if it were an instance of C<$class_name>. If C<$constructor> is not
80             given, the object will be build by directly Cing it into the given
81             C<$class_name>. If C<$constructor> is given, this method will be used as the
82             constructor method name, which will be called with the given C<@args> (if any).
83            
84             As a debug helper, if C<$Object::Recorder::BYPASS_STORAGE> is set, this method
85             will skip the recording process and call the constructor directly. This can be
86             helpful is something's going wrong.
87            
88             =cut
89            
90             sub record {
91 0     0 1   my $class = shift;
92 0 0         return $class->create_storage( @_ )
93             unless $BYPASS_STORAGE;
94            
95             # debug mode: don't record, execute directly
96 0           my ($rec_class, $constructor, @args) = @_;
97             return
98 0 0         $constructor ?
99             $rec_class->$constructor(@args) : bless {}, $rec_class;
100             }
101            
102             =head2 replay
103            
104             Replays the storage object, performing the stored method calls.
105            
106             =cut
107            
108             sub replay {
109 0     0 1   my $class = shift;
110 0           my ($store, $obj) = @_;
111            
112 0           my $retval = $class->_replay($store, $obj);
113            
114             # cleanup the internal cache after replay calls
115 0           Memoize::flush_cache('_evaluate_arg');
116 0           Memoize::flush_cache('_get_obj');
117            
118 0           return $retval;
119             }
120            
121             =head2 storage_class
122            
123             Should return the storage object's class name (used by C).
124            
125             =cut
126            
127 0     0 1   sub storage_class { 'Object::Recorder::Storage' }
128            
129             =head2 create_storage
130            
131             Should return a suitable storage object.
132            
133             =cut
134            
135 0     0 1   sub create_storage { shift->storage_class->new(@_) }
136            
137             # private methods
138             sub _evaluate_arg {
139             my $class = shift;
140             my ($obj) = @_;
141            
142             return $obj
143             unless ref $obj eq $class->storage_class;
144            
145             return $class->_replay($obj);
146             }
147            
148             # memoizes the appropriate methods
149             memoize('_evaluate_arg');
150             memoize('_get_obj');
151            
152             sub _get_obj {
153             my $class = shift;
154             my ($store, $obj) = @_;
155            
156             unless (defined $obj) {
157             my $object_class = $store->{object_class};
158            
159             # make sure the module is loaded
160             eval "require $object_class;";
161            
162             if (my $constructor = $store->{constructor}) {
163            
164             my @args = map { $class->_evaluate_arg($_) } @{$store->{args}};
165            
166             # we should call a constructor method
167             $obj = $object_class->$constructor( @args );
168             }
169             else {
170             # otherwise, create a standard object
171             $obj = bless {}, $object_class;
172             }
173             }
174            
175             $store->{expansion_pending} = 1;
176            
177             return $obj;
178             }
179            
180             sub _replay {
181 0     0     my $class = shift;
182 0           my ($store, $obj) = @_;
183            
184             # return if we're not calling an specific constructor and also don't have
185             # any recorded calls
186 0 0 0       return if @{$store->{calls}} == 0 and not $store->{constructor};
  0            
187            
188 0 0 0       die "no object nor object class"
189             if not defined $obj and not $store->{object_class};
190            
191             # only create the object if we don't have one yet
192 0           $obj = $class->_get_obj($store, $obj);
193            
194             # only expand the object if it's not already being expanded
195 0 0         if ($store->{expansion_pending}) {
196            
197 0           $store->{expansion_pending} = 0;
198            
199 0           for my $call (@{$store->{calls}}) {
  0            
200            
201 0           my $method = $call->{method};
202            
203             # expand stored args
204 0           my @args = map { $class->_evaluate_arg($_) } @{$call->{args}};
  0            
  0            
205            
206             # recursively replay objects
207 0           $class->_replay($call->{retval}, $obj->$method(@args));
208             }
209             }
210            
211 0           return $obj;
212             }
213            
214             =head1 LIMITATIONS
215            
216             Currently, this module currently only supports methods calls which have up to a
217             single return value and it also doesn't play well with context-sensitive return
218             values. This limitation could be worked around but would involve some trickery.
219             I'll probably try implementing it if someone asks me to, but it works good
220             enough for my own needs.
221            
222             It won't record direct accesses to the object, only method calls. So if you
223             need to fiddle with the object's internal attributes, this module won't work
224             for you. Implementing this would require Cing the storage object and
225             recording that, too.
226            
227             In other words:
228            
229             $obj->{some_field} = 'some_value';
230            
231             won't be recorded.
232            
233             Patches are welcome.
234            
235             =head1 BUGS
236            
237             Please report any bugs or feature requests to
238             C, or through the web interface at
239             L.
240             I will be notified, and then you'll automatically be notified of progress on
241             your bug as I make changes.
242            
243             =head1 AUTHOR
244            
245             Nilson Santos Figueiredo Junior, C<< >>
246            
247             =head1 COPYRIGHT & LICENSE
248            
249             Copyright (C) 2007 Nilson Santos Figueiredo Junior.
250             Copyright (C) 2007 Picturetrail, Inc.
251            
252             This program is free software; you can redistribute it and/or modify it
253             under the same terms as Perl itself.
254            
255             =cut
256            
257             1;