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