1package Workflow::Persister::File; 2 3use warnings; 4use strict; 5use base qw( Workflow::Persister ); 6use Data::Dumper qw( Dumper ); 7use File::Spec::Functions qw( catdir catfile ); 8use Log::Log4perl qw( get_logger ); 9use Workflow::Exception qw( configuration_error persist_error ); 10use Workflow::Persister::RandomId; 11use File::Slurp qw(slurp); 12use English qw( -no_match_vars ); 13 14$Workflow::Persister::File::VERSION = '1.59'; 15 16my @FIELDS = qw( path ); 17__PACKAGE__->mk_accessors(@FIELDS); 18 19sub init { 20 my ( $self, $params ) = @_; 21 $self->SUPER::init($params); 22 unless ( $self->use_uuid eq 'yes' || $self->use_random eq 'yes' ) { 23 $self->use_random('yes'); 24 } 25 $self->assign_generators($params); 26 unless ( $params->{path} ) { 27 configuration_error "The file persister must have the 'path' ", 28 "specified in the configuration"; 29 } 30 unless ( -d $params->{path} ) { 31 configuration_error "The file persister must have a valid directory ", 32 "specified in the 'path' key of the configuration ", 33 "(given: '$params->{path}')"; 34 } 35 $self->log->info( 36 "Using path for workflows and histories '$params->{path}'"); 37 $self->path( $params->{path} ); 38} 39 40sub create_workflow { 41 my ( $self, $wf ) = @_; 42 my $generator = $self->workflow_id_generator; 43 my $wf_id = $generator->pre_fetch_id(); 44 $wf->id($wf_id); 45 $self->log->debug("Generated workflow ID '$wf_id'"); 46 $self->_serialize_workflow($wf); 47 my $full_history_path = $self->_get_history_path($wf); 48 ## no critic (ProhibitMagicNumbers) 49 mkdir( $full_history_path, 0777 ) 50 || persist_error "Cannot create history dir '$full_history_path': $!"; 51 52 return $wf_id; 53} 54 55sub fetch_workflow { 56 my ( $self, $wf_id ) = @_; 57 my $full_path = $self->_get_workflow_path($wf_id); 58 $self->log->debug("Checking to see if workflow exists in '$full_path'"); 59 unless ( -f $full_path ) { 60 $self->log->error("No file at path '$full_path'"); 61 persist_error "No workflow with ID '$wf_id' is available"; 62 } 63 $self->log->debug("File exists, reconstituting workflow"); 64 my $wf_info = eval { $self->constitute_object($full_path) }; 65 if ($EVAL_ERROR) { 66 persist_error "Cannot reconstitute data from file for ", 67 "workflow '$wf_id': $EVAL_ERROR"; 68 } 69 return $wf_info; 70} 71 72sub update_workflow { 73 my ( $self, $wf ) = @_; 74 $self->_serialize_workflow($wf); 75} 76 77sub create_history { 78 my ( $self, $wf, @history ) = @_; 79 my $generator = $self->history_id_generator; 80 my $history_dir = $self->_get_history_path($wf); 81 $self->log->info("Will use directory '$history_dir' for history"); 82 foreach my $history (@history) { 83 if ( $history->is_saved ) { 84 $self->log->debug("History object saved, skipping..."); 85 next; 86 } 87 $self->log->debug("History object unsaved, continuing..."); 88 my $history_id = $generator->pre_fetch_id(); 89 $history->id($history_id); 90 my $history_file = catfile( $history_dir, $history_id ); 91 $self->serialize_object( $history_file, $history ); 92 $self->log->info("Created history object '$history_id' ok"); 93 $history->set_saved(); 94 } 95} 96 97sub fetch_history { 98 my ( $self, $wf ) = @_; 99 my $history_dir = $self->_get_history_path($wf); 100 $self->log->debug("Trying to read history files from dir '$history_dir'"); 101 opendir( HISTORY, $history_dir ) 102 || persist_error "Cannot read history from '$history_dir': $!"; 103 my @history_files = grep { -f $_ } 104 map { catfile( $history_dir, $_ ) } readdir HISTORY; 105 closedir HISTORY; 106 my @histories = (); 107 108 foreach my $history_file (@history_files) { 109 $self->log->debug("Reading history from file '$history_file'"); 110 my $history = $self->constitute_object($history_file); 111 $history->set_saved(); 112 push @histories, $history; 113 } 114 return @histories; 115} 116 117sub _serialize_workflow { 118 my ( $self, $wf ) = @_; 119 local $Data::Dumper::Indent = 1; 120 my $full_path = $self->_get_workflow_path( $wf->id ); 121 $self->log->debug("Trying to write workflow to '$full_path'"); 122 my %wf_info = ( 123 id => $wf->id, 124 state => $wf->state, 125 last_update => $wf->last_update, 126 type => $wf->type, 127 context => $wf->context, 128 129 ); 130 $self->serialize_object( $full_path, \%wf_info ); 131 $self->log->debug("Wrote workflow ok"); 132} 133 134sub serialize_object { 135 my ( $self, $path, $object ) = @_; 136 $self->log->info( "Trying to save object of type '", 137 ref($object), "' ", "to path '$path'" ); 138 open( THINGY, '>', $path ) 139 || persist_error "Cannot write to '$path': $!"; 140 print THINGY Dumper($object) 141 || persist_error "Error writing to '$path': $!"; 142 close(THINGY) || persist_error "Cannot close '$path': $!"; 143 $self->log->debug("Wrote object to file ok"); 144} 145 146sub constitute_object { 147 my ( $self, $object_path ) = @_; 148 149 my $content = slurp($object_path); 150 151 no strict; 152 my $object = eval $content; 153 croak $EVAL_ERROR if ($EVAL_ERROR); 154 return $object; 155 156} 157 158sub _get_workflow_path { 159 my ( $self, $wf_id ) = @_; 160 $self->log->info( "Creating workflow file from '", 161 $self->path, "' ", "and ID '$wf_id'" ); 162 return catfile( $self->path, $wf_id . '_workflow' ); 163} 164 165sub _get_history_path { 166 my ( $self, $wf ) = @_; 167 return catdir( $self->path, $wf->id . '_history' ); 168} 169 1701; 171 172__END__ 173 174=pod 175 176=head1 NAME 177 178Workflow::Persister::File - Persist workflow and history to the filesystem 179 180=head1 VERSION 181 182This documentation describes version 1.59 of this package 183 184=head1 SYNOPSIS 185 186 <persister name="MainPersister" 187 class="Workflow::Persister::File" 188 path="/home/workflow/storage"/> 189 190=head1 DESCRIPTION 191 192Main persistence class for storing the workflow and workflow history 193records to a filesystem for later retrieval. Data are stored in 194serialized Perl data structure files. 195 196=head2 METHODS 197 198=head3 constitute_object 199 200This method deserializes an object. 201 202Takes a single parameter of an filesystem path pointing to an object 203 204Returns the re-instantiated object or dies. 205 206=head3 create_history 207 208Serializes history records associated with a workflow object 209 210Takes two parameters: a workflow object and an array of workflow history objects 211 212Returns: provided array of workflow history objects upon success 213 214=head3 create_workflow 215 216Serializes a workflow into the persistance entity configured by our workflow. 217 218Takes a single parameter: a workflow object 219 220Returns a single value, a id for unique identification of out serialized 221workflow for possible deserialization. 222 223=head3 fetch_history 224 225Deserializes history records associated with a workflow object 226 227Takes a single parameter: a workflow object 228 229Returns an array of workflow history objects upon success 230 231=head3 fetch_workflow 232 233Deserializes a workflow from the persistance entity configured by our workflow. 234 235Takes a single parameter: the unique id assigned to our workflow upon 236serialization (see L</create_workflow>). 237 238Returns a hashref consisting of two keys: 239 240=over 241 242=item * state, the workflows current state 243 244=item * last_update, date indicating last update 245 246=back 247 248=head3 init ( \%params ) 249 250Method to initialize the persister object. Sets up the configured generators 251 252Throws a L<Workflow::Exception> if a valid filesystem path is not provided with 253the parameters. 254 255=head3 serialize_object 256 257Method that writes a given object to a given path. 258 259Takes two parameters: path (a filesystem path) and an object 260 261Throws L<Workflow::Exception> if unable to serialize the given object to the 262given path. 263 264Returns: Nothing 265 266=head3 update_workflow 267 268Updates a serialized workflow in the persistance entity configured by our 269workflow. 270 271Takes a single parameter: a workflow object 272 273Returns: Nothing 274 275=head1 TODO 276 277=over 278 279=item * refactor L</constitute_object>, no checks are made on filesystem prior 280to deserialization attempt. 281 282=back 283 284=head1 SEE ALSO 285 286=over 287 288=item * L<Workflow::Persister> 289 290=back 291 292=head1 COPYRIGHT 293 294Copyright (c) 2003-2022 Chris Winters. All rights reserved. 295 296This library is free software; you can redistribute it and/or modify 297it under the same terms as Perl itself. 298 299Please see the F<LICENSE> 300 301=head1 AUTHORS 302 303Please see L<Workflow> 304 305=cut 306