File Coverage

blib/lib/Class/Prevayler.pm
Criterion Covered Total %
statement 54 99 54.5
branch 0 18 0.0
condition 0 6 0.0
subroutine 17 28 60.7
pod 2 4 50.0
total 73 155 47.1


line stmt bran cond sub pod time code
1              
2             package Class::Prevayler;
3 1     1   31637 use strict;
  1         2  
  1         38  
4 1     1   6 use warnings;
  1         3  
  1         37  
5 1     1   1132 use File::Sync qw(fsync sync);
  1         5145  
  1         73  
6 1     1   9 use File::Spec;
  1         2  
  1         22  
7 1     1   996 use Data::Dumper;
  1         10428  
  1         238  
8 1     1   9 use Carp;
  1         2  
  1         55  
9 1     1   544 use Class::Prevayler::SystemRecoverer;
  1         3  
  1         59  
10 1     1   654 use Class::Prevayler::CommandLogger;
  1         21  
  1         47  
11 1     1   557 use Class::Prevayler::FileCounter;
  1         2063  
  1         220  
12              
13             use constant INSTANCE_DEFAULTS => (
14             sync_after_write => 1,
15             directory => './',
16             serializer => sub {
17 0         0 local ( $Data::Dumper::Indent = 0 );
18 0         0 local ( $Data::Dumper::Purity = 1 );
19 0         0 return Data::Dumper->Dump( [ $_[0] ], ['dumped'] );
20             },
21             deserializer => sub {
22 0         0 my $dumped;
23 0         0 eval $_[0];
24 0         0 return $dumped;
25             },
26 1     1   8 );
  1         2  
  1         78  
27              
28             BEGIN {
29 1     1   5 use Exporter ();
  1         1  
  1         20  
30 1     1   5 use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         1  
  1         135  
31 1     1   4509 $VERSION = 0.02;
32 1         16 @ISA = qw (Exporter);
33 1         3 @EXPORT = qw ();
34 1         1 @EXPORT_OK = qw ();
35 1         546 %EXPORT_TAGS = ();
36              
37             use Class::MethodMaker
38 1         6 new_with_init => 'new',
39             new_hash_init => 'hash_init',
40             get_set => [
41             'sync_after_write', 'directory',
42             'serializer', 'deserializer',
43             'system', '_started',
44             '_system_recoverer', '_command_logger',
45             '_file_counter',
46 1     1   3 ];
  1         2  
47             }
48              
49             =head1 NAME
50              
51             Class::Prevayler - Prevayler implementation - www.prevayler.org
52              
53             =head1 SYNOPSIS
54              
55             use Class::Prevayler;
56              
57             my $prevayler = Class::Prevayler->new(
58             system => new Your::System,
59             directory => 'prevayler/demo/'
60             );
61              
62             $prevayler->start();
63              
64             my $cmd_obj = Your::Command::Object->new();
65              
66             $prevayler->execute( $cmd_obj );
67              
68             $prevayler->take_snapshot();
69              
70             =head1 DESCRIPTION
71              
72             THIS IS BETA-SOFTWARE!!
73              
74             Class::Prevayler - aka 'Perlvayler' - is a Perl implementation of the prevayler concept.
75              
76             You can find an introduction to this concept on www.prevayler.org.
77              
78             This module overloads the 'time', 'localtime' and 'gmtime' functions to make the system deterministic.
79              
80             =head1 USAGE
81              
82              
83             =head2 new
84              
85             Usage : $prevayler->new()
86             Purpose : creates a new object
87             Returns : the new prevayler-object
88             Argument : you can use key-value pairs to initialize the attributes
89              
90             =cut
91              
92             sub init {
93 1     1 0 57 my $self = shift;
94 1         10 my %values = ( INSTANCE_DEFAULTS, @_ );
95 1         29 $self->hash_init(%values);
96 1         407 return;
97             }
98              
99             =head2 start
100              
101             Usage : $prevayler->start()
102             Purpose : recovers the old system state
103             Returns : nothing
104             Argument : none
105             Comments : You have to call it before you can use execute(), even if there is no old serialized state
106              
107             =cut
108              
109             sub start {
110 0     0 1   my $self = shift;
111 0 0         $self->_system_recoverer()
112             || $self->_system_recoverer(
113             Class::Prevayler::SystemRecoverer->new(
114             directory => $self->directory(),
115             deserializer => $self->deserializer(),
116             )
117             );
118              
119 0           my $system = $self->_system_recoverer()->recover( $self->system );
120              
121             # TODO: create dir if needed
122              
123 0           $self->_file_counter(
124             Class::Prevayler::FileCounter->new(
125             next_logfile_number => $self->_system_recoverer->next_logfile_number
126             )
127             );
128              
129 0           $self->system($system);
130 0 0         $self->_command_logger()
131             || $self->_command_logger(
132             Class::Prevayler::CommandLogger->new(
133             directory => $self->directory(),
134             serializer => $self->serializer(),
135             file_counter => $self->_file_counter(),
136             )
137             );
138 0           $self->_started(1);
139             }
140              
141             sub execute {
142             =head2 start
143              
144             Usage : $prevayler->execute()
145             Purpose : execute one command object on the system, and log it
146             Returns : nothing
147             Argument : command object
148             Comments : all command objects must implement a 'execute()' method
149              
150             =cut
151 0     0 0   my ( $self, $cmd_obj ) = @_;
152 0 0         croak "call start() first\n" unless ( $self->_started() );
153 0           my $cmd_obj_clock_recovery =
154             Class::Prevayler::ClockRecoveryCommand->new($cmd_obj);
155 0           $self->_command_logger->write_command($cmd_obj_clock_recovery);
156 0           $self->_execute_cmd($cmd_obj_clock_recovery);
157              
158 0           return 1;
159             }
160              
161             sub _execute_cmd {
162 0     0     my ( $self, $cmd_obj ) = @_;
163 0           $cmd_obj->execute( $self->system() );
164             }
165              
166             =head2 take_snapshot
167              
168             Usage : $prevayler->take_snapshot()
169             Purpose : produce a serialized image of the system
170             Returns : nothing
171             Argument : command object
172             Comments : all command objects must implement a 'execute()' method
173              
174             =cut
175             sub take_snapshot {
176 0     0 1   my ($self) = @_;
177 0           my $filename = File::Spec->catfile( $self->directory,
178             sprintf( '%016d', $self->_file_counter->reserve_number_for_snapshot )
179             . '.snapshot' );
180 0           local (*FILEHANDLE);
181 0 0 0       open( FILEHANDLE, ">$filename" )
182             and print FILEHANDLE $self->serializer()->( $self->system() )
183             or croak "Couldn't write file $filename : $!";
184 0 0 0       ( $self->sync_after_write() && fsync(*FILEHANDLE) && sync() );
185 0 0         close FILEHANDLE
186             or croak "Couldn't close file $filename : $!";
187 0           return;
188             }
189              
190              
191             1; #this line is important and will help the module return a true value
192              
193             =head2 system
194              
195             Usage : $prevayler->system( new My::System )
196             my $system = $prevayler->system();
197             Purpose : access to the prevalent system
198             Returns : returns the actual system if called without argument
199             Argument : new prevalent system
200              
201              
202             =head2 directory
203              
204             Usage : $prevayler->directory( './prevayler/' )
205             my $directory = $prevayler->directory();
206             Purpose : sets the directory where all serialized objects are stored
207             Returns : returns the actual directory if called without argument
208             Argument : new directory
209              
210              
211             =head2 serializer
212              
213             Usage : $prevayler->serializer( \&mySerializer )
214             my $serializer = $prevayler->serializer();
215             Purpose : define the serializer.
216             The serializer is called with a structure (an object)
217             and returns a string representation of this structure.
218             The default serializer is implemented with Data::Dumper.
219             Returns : returns the actual serializer if called without argument
220             Argument : reference to a subroutine
221              
222             =head2 deserializer
223              
224             Usage : $prevayler->deserializer( \&myDeSerializer )
225             my $deserializer = $prevayler->deserializer();
226             Purpose : define the deserializer.
227             The deserializer is called with a serialized structure
228             and returns this structure.
229             The default deserializer is implemented with eval.
230             Returns : returns the actual deserializer if called without argument
231             Argument : reference to a subroutine
232              
233             =head1 BUGS
234              
235             - none known, but: this is beta-software, there will be API and fileformat changes.
236              
237              
238             =head1 AUTHOR
239              
240             Nathanael Obermayer
241             CPAN ID: nathanael
242             natom-pause@smi2le.net
243              
244             =head1 COPYRIGHT
245              
246             This program is free software; you can redistribute
247             it and/or modify it under the same terms as Perl itself.
248              
249             The full text of the license can be found in the
250             LICENSE file included with this module.
251              
252              
253             =head1 SEE ALSO
254              
255             perl(1).
256              
257             http://www.prevayler.org
258              
259             =cut
260              
261             #=head2 sync_after_write
262             #
263             # Usage : $prevayler->sync_after_write( 1 )
264             # my $sync_state = $prevayler->sync_after_write();
265             # Purpose : switches syncing on or off... trade security for speed
266             # Returns : returns the actual state if called without argument
267             # Argument : new state ( a false or true value )
268             #
269             #
270             package Class::Prevayler::ClockRecoveryCommand;
271              
272             BEGIN {
273 1     1   6 use Class::MethodMaker get_set => [ '_cmd_obj', '_time', ];
  1         1  
  1         6  
274 1     1   1086 *CORE::GLOBAL::time =
275             \&Class::Prevayler::ClockRecoveryCommand::_prevayler_time;
276 1         2 *CORE::GLOBAL::localtime =
277             \&Class::Prevayler::ClockRecoveryCommand::_prevayler_localtime;
278 1         320 *CORE::GLOBAL::gmtime =
279             \&Class::Prevayler::ClockRecoveryCommand::_prevayler_gmtime;
280             }
281              
282             sub new {
283 0     0     my ( $pkg, $cmd_obj ) = @_;
284 0           my $self = bless( {}, $pkg );
285              
286 0           $self->_cmd_obj($cmd_obj);
287              
288             # store the time
289 0           $self->_time(CORE::time);
290              
291 0           return $self;
292             }
293              
294             sub execute {
295 0     0     my ( $self, $system ) = @_;
296 0           $self->_freeze_time;
297 0           $self->_cmd_obj()->execute($system);
298 0           $self->_thaw_time;
299             }
300              
301             sub _freeze_time {
302 0     0     my $self = shift;
303 0           $Class::Prevayler::ClockRecoveryCommand::time_frozen = 1;
304 0           $Class::Prevayler::ClockRecoveryCommand::time = $self->_time;
305             }
306              
307             sub _thaw_time {
308 0     0     undef $Class::Prevayler::ClockRecoveryCommand::time;
309 0           $Class::Prevayler::ClockRecoveryCommand::time_frozen = 0;
310             }
311              
312             sub _prevayler_time {
313 0 0   0     $Class::Prevayler::ClockRecoveryCommand::time_frozen
314             ? $Class::Prevayler::ClockRecoveryCommand::time
315             : CORE::time;
316             }
317              
318             sub _prevayler_localtime {
319             wantarray
320 0 0   0     ? ( CORE::localtime( time() ) )
321             : scalar CORE::localtime( time() );
322             }
323              
324             sub _prevayler_gmtime {
325             wantarray
326 0 0   0     ? ( CORE::gmtime( time() ) )
327             : scalar CORE::gmtime( time() );
328             }
329             1;
330             __END__