File Coverage

lib/Workflow/Factory.pm
Criterion Covered Total %
statement 371 440 84.3
branch 106 140 75.7
condition 22 38 57.8
subroutine 43 50 86.0
pod 17 17 100.0
total 559 685 81.6


line stmt bran cond sub pod time code
1             package Workflow::Factory;
2              
3 20     20   1650424 use warnings;
  20         108  
  20         803  
4 20     20   140 use strict;
  20         43  
  20         578  
5 20     20   112 use base qw( Workflow::Base );
  20         45  
  20         4941  
6 20     20   1160 use DateTime;
  20         573714  
  20         692  
7 20     20   126 use Log::Log4perl qw( get_logger );
  20         36  
  20         150  
8 20     20   5268 use Workflow::Exception qw( configuration_error workflow_error );
  20         52  
  20         1494  
9 20     20   172 use Carp qw(croak);
  20         60  
  20         1167  
10 20     20   5976 use English qw( -no_match_vars );
  20         20299  
  20         120  
11             $Workflow::Factory::VERSION = '1.62';
12              
13             # Extra action attribute validation is off by default for compatibility.
14             our $VALIDATE_ACTION_CONFIG = 0;
15              
16             my (%INSTANCES);
17              
18             sub import {
19 95     95   277749 my $class = shift;
20              
21 95   33     640 $class = ref $class || $class; # just in case
22 95         271 my $package = caller;
23 95         1164 my $log = get_logger(__PACKAGE__);
24 95 100 66     13929 if ( defined $_[0] && $_[0] eq 'FACTORY' ) {
25 93         150 shift;
26 93         152 my $instance;
27              
28 93         230 my $import_target = $package . '::FACTORY';
29 20     20   9620 no strict 'refs';
  20         79  
  20         33286  
30 93 50       176 unless ( defined &{$import_target} ) {
  93         588  
31 93         439 *{$import_target} = sub {
32 23 100   23   11494 return $instance if $instance;
33 16         72 $instance = _initialize_instance($class);
34 16         98 return $instance;
35 93         441 };
36             }
37             }
38 95         538 $class->SUPER::import(@_);
39             }
40              
41             require Workflow;
42             require Workflow::Action;
43             require Workflow::Condition;
44             require Workflow::Condition::Negated;
45             require Workflow::Config;
46             require Workflow::Context;
47             require Workflow::History;
48             require Workflow::Persister;
49             require Workflow::State;
50             require Workflow::Validator;
51              
52             my $DEFAULT_INITIAL_STATE = 'INITIAL';
53              
54             my @FIELDS = qw(config_callback);
55              
56             __PACKAGE__->mk_accessors(@FIELDS);
57              
58             sub new {
59 2     2 1 1566 my $proto = shift;
60 2   33     20 my $class = ref $proto || $proto;
61              
62 2         16 workflow_error "Please call 'instance()' or import the 'FACTORY' object ",
63             "to get the '$class' object rather than instantiating a ",
64             "new one directly.";
65             }
66              
67             sub instance {
68 30     30 1 30625 my $proto = shift;
69 30   33     193 my $class = ref $proto || $proto;
70              
71 30         113 return _initialize_instance($class);
72             }
73              
74             sub _initialize_instance {
75 46     46   128 my ($class) = @_;
76              
77 46         173 my $log = get_logger(__PACKAGE__);
78 46 100       1937 unless ( $INSTANCES{$class} ) {
79 17         136 $log->debug( "Creating empty instance of '$class' factory for ",
80             "singleton use" );
81 17         9218 my $instance = bless {} => $class;
82 17         192 $instance->init();
83 17         45 $INSTANCES{$class} = $instance;
84             }
85 46         158 return $INSTANCES{$class};
86             }
87              
88             sub _delete_instance {
89 0     0   0 my ($class) = @_;
90              
91 0         0 my $log = get_logger(__PACKAGE__);
92 0 0       0 if ( $INSTANCES{$class} ) {
93 0         0 $log->debug( "Deleting instance of '$class' factory." );
94 0         0 delete $INSTANCES{$class};
95             } else {
96 0         0 $log->debug( "No instance of '$class' factory found." );
97             }
98              
99 0         0 return;
100             }
101              
102             my %CONFIG = ( 'Workflow::Config' => 1 );
103              
104             sub add_config_from_file {
105 16     16 1 2923 my ( $self, %params ) = @_;
106 16 50       97 return unless ( scalar keys %params );
107              
108 16         87 _check_config_keys(%params);
109              
110 16         120 foreach my $type ( sort keys %params ) {
111             $self->log->debug(
112             sub { "Using '$type' configuration file(s): " .
113 57     54   13291 join( ', ', _flatten( $params{$type} ) ) } );
  54         1387  
114             }
115              
116 16         4965 $self->log->debug( "Adding condition configurations..." );
117              
118 16 100       5180 if ( ref $params{condition} eq 'ARRAY' ) {
119 10         25 foreach my $condition ( @{ $params{condition} } ) {
  10         47  
120 20         3619 $self->_add_condition_config(
121             Workflow::Config->parse_all_files( 'condition', $condition )
122             );
123             }
124             } else {
125             $self->_add_condition_config(
126             Workflow::Config->parse_all_files(
127             'condition', $params{condition}
128             )
129 6         46 );
130             }
131              
132 16         4975 $self->log->debug( "Adding validator configurations..." );
133              
134 16 100       5196 if ( ref $params{validator} eq 'ARRAY' ) {
135 1         2 foreach my $validator ( @{ $params{validator} } ) {
  1         4  
136 2         386 $self->_add_validator_config(
137             Workflow::Config->parse_all_files( 'validator', $validator )
138             );
139             }
140             } else {
141             $self->_add_validator_config(
142             Workflow::Config->parse_all_files(
143             'validator', $params{validator}
144             )
145 15         171 );
146             }
147              
148 16         4697 $self->log->debug( "Adding persister configurations..." );
149              
150 16 50       5335 if ( ref $params{persister} eq 'ARRAY' ) {
151 0         0 foreach my $persister ( @{ $params{persister} } ) {
  0         0  
152 0         0 $self->_add_persister_config(
153             Workflow::Config->parse_all_files( 'persister', $persister )
154             );
155             }
156             } else {
157             $self->_add_persister_config(
158             Workflow::Config->parse_all_files(
159             'persister', $params{persister}
160             )
161 16         191 );
162             }
163              
164 16         92 $self->log->debug( "Adding action configurations..." );
165              
166 16 100       5175 if ( ref $params{action} eq 'ARRAY' ) {
167 10         31 foreach my $action ( @{ $params{action} } ) {
  10         40  
168 21         174 $self->_add_action_config(
169             Workflow::Config->parse_all_files( 'action', $action ) );
170             }
171             } else {
172             $self->_add_action_config(
173 6         40 Workflow::Config->parse_all_files( 'action', $params{action} ) );
174             }
175              
176 16         97 $self->log->debug( "Adding workflow configurations..." );
177              
178 16 100       5193 if ( ref $params{workflow} eq 'ARRAY' ) {
179 10         164 foreach my $workflow ( @{ $params{workflow} } ) {
  10         138  
180 28         217 $self->_add_workflow_config(
181             Workflow::Config->parse_all_files( 'workflow', $workflow ) );
182             }
183             } else {
184             $self->_add_workflow_config(
185             Workflow::Config->parse_all_files(
186             'workflow', $params{workflow}
187             )
188 6         57 );
189             }
190              
191 16         144 return;
192             }
193              
194             sub add_config {
195 25     25 1 23068 my ( $self, %params ) = @_;
196 25 50       142 return unless ( scalar keys %params );
197 25         127 _check_config_keys(%params);
198 25         129 $self->_add_condition_config( _flatten( $params{condition} ) );
199 25         440 $self->_add_validator_config( _flatten( $params{validator} ) );
200 25         484 $self->_add_persister_config( _flatten( $params{persister} ) );
201 25         4492 $self->_add_action_config( _flatten( $params{action} ) );
202 24         112 $self->_add_workflow_config( _flatten( $params{workflow} ) );
203 23         109 return;
204             }
205              
206             sub _check_config_keys {
207 41     41   190 my (%params) = @_;
208             my @bad_keys
209 41         255 = grep { !Workflow::Config->is_valid_config_type($_) } keys %params;
  89         744  
210 41 50       216 if ( scalar @bad_keys ) {
211 0         0 workflow_error "You tried to add configuration information to the ",
212             "workflow factory with one or more bad keys: ",
213             join( ', ', @bad_keys ), ". The following are the ",
214             "keys you have to choose from: ",
215             join( ', ', Workflow::Config->get_valid_config_types ), '.';
216             }
217             }
218              
219             sub _flatten {
220 178     178   450 my ($item) = @_;
221 178 100       937 return ( ref $item eq 'ARRAY' ) ? @{$item} : ($item);
  49         312  
222             }
223              
224             ########################################
225             # WORKFLOW
226              
227             sub _add_workflow_config {
228 58     58   223 my ( $self, @all_workflow_config ) = @_;
229 58 50       204 return unless ( scalar @all_workflow_config );
230              
231 58         148 foreach my $workflow_config (@all_workflow_config) {
232 58 100       241 next unless ( ref $workflow_config eq 'HASH' );
233 42         141 my $wf_type = $workflow_config->{type};
234 42         223 $self->{_workflow_config}{$wf_type} = $workflow_config;
235              
236             # Create Workflow::State objects for each configured state.
237             # When we instantiate a new workflow we pass these objects
238              
239 42         192 foreach my $state_conf ( @{ $workflow_config->{state} } ) {
  42         126  
240              
241             # Add the workflow type to the state conf.
242 138         288 $state_conf->{type} = $wf_type;
243 138         661 my $wf_state = Workflow::State->new( $state_conf, $self );
244              
245 138         192 push @{ $self->{_workflow_state}{$wf_type} }, $wf_state;
  138         486  
246             }
247              
248 42         174 my $wf_class = $workflow_config->{class};
249 42 100       145 if ( $wf_class ) {
250 1         7 $self->_load_class( $wf_class,
251             q{Cannot require workflow class '%s': %s} );
252             }
253 42         165 $self->_load_observers($workflow_config);
254              
255 41         160 $self->log->info( "Added all workflow states..." );
256             }
257              
258 57         12935 return;
259             }
260              
261             # Load all the observers so they're available when we instantiate the
262             # workflow
263              
264             sub _load_observers {
265 42     42   110 my ( $self, $workflow_config ) = @_;
266 42         92 my $wf_type = $workflow_config->{type};
267 42   100     210 my $observer_specs = $workflow_config->{observer} || [];
268 42         125 my @observers = ();
269 42         86 foreach my $observer_info ( @{$observer_specs} ) {
  42         221  
270 3 100       18 if ( my $observer_class = $observer_info->{class} ) {
    50          
271 1         8 $self->_load_class( $observer_class,
272             "Cannot require observer '%s' to watch observer "
273             . "of type '$wf_type': %s" );
274 1     9   10 push @observers, sub { $observer_class->update(@_) };
  9         106  
275             } elsif ( my $observer_sub = $observer_info->{sub} ) {
276 2         18 my ( $observer_class, $observer_sub )
277             = $observer_sub =~ /^(.*)::(.*)$/;
278 2         14 $self->_load_class( $observer_class,
279             "Cannot require observer '%s' with sub '$observer_sub' to "
280             . "watch observer of type '$wf_type': %s" );
281 2         11 my $o_sub_name = $observer_class . '::' . $observer_sub;
282 2 100       9 if (exists &$o_sub_name) {
283 20     20   183 no strict 'refs';
  20         52  
  20         38772  
284 1         2 push @observers, \&{ $o_sub_name };
  1         5  
285             } else {
286 1         4 my $error = 'subroutine not found';
287 1         5 $self->log->error( "Error loading subroutine '$observer_sub' in ",
288             "class '$observer_class': $error" );
289 1         724 workflow_error $error;
290             }
291             } else {
292 0         0 workflow_error "Cannot add observer to '$wf_type': you must ",
293             "have either 'class' or 'sub' defined. (See ",
294             "Workflow::Factory docs for details.)";
295             }
296             }
297              
298 41         87 my $observers_num = scalar @observers;
299              
300 41 100       264 if (@observers) {
301 1         4 $self->{_workflow_observers}{$wf_type} = \@observers;
302              
303             $self->log->info(
304 1     1   27 sub { "Added $observers_num to '$wf_type': " .
305 1         5 join( ', ', @observers ) } );
306              
307             } else {
308 40         131 $self->{_workflow_observers}{$wf_type} = undef;
309              
310 40         163 $self->log->info( "No observers added to '$wf_type'" );
311             }
312              
313 41         12893 return $observers_num;
314             }
315              
316             sub _load_class {
317 4     4   11 my ( $self, $class_to_load, $msg ) = @_;
318              
319 4         6 local $EVAL_ERROR = undef;
320 4         219 eval "require $class_to_load";
321 4 50       684 if ($EVAL_ERROR) {
322 0         0 my $full_msg = sprintf $msg, $class_to_load, $EVAL_ERROR;
323 0         0 $self->log->error($full_msg);
324 0         0 workflow_error $full_msg;
325             }
326              
327             }
328              
329             sub create_workflow {
330 23     23 1 16460 my ( $self, $wf_type, $context, $wf_class ) = @_;
331 23         118 my $wf_config = $self->_get_workflow_config($wf_type);
332              
333 23 100       82 unless ($wf_config) {
334 2         12 workflow_error "No workflow of type '$wf_type' available";
335             }
336              
337 21 50 100     190 $wf_class = $wf_config->{class} || 'Workflow' unless ($wf_class);
338             my $wf
339             = $wf_class->new( undef,
340             $wf_config->{initial_state} || $DEFAULT_INITIAL_STATE,
341 21   33     605 $wf_config, $self->{_workflow_state}{$wf_type}, $self );
342 21   33     345 $wf->context( $context || Workflow::Context->new );
343 21         116 $wf->last_update( DateTime->now( time_zone => $wf->time_zone() ) );
344 21         135 $self->log->info( "Instantiated workflow object properly, persisting..." );
345 21         6520 my $persister = $self->get_persister( $wf_config->{persister} );
346 21         159 my $id = $persister->create_workflow($wf);
347 21         333 $wf->id($id);
348 21         116 $self->log->info("Persisted workflow with ID '$id'; creating history...");
349 21         6600 $persister->create_history(
350             $wf,
351             Workflow::History->new(
352             { workflow_id => $id,
353             action => $persister->get_create_action($wf),
354             description => $persister->get_create_description($wf),
355             user => $persister->get_create_user($wf),
356             state => $wf->state,
357             date => DateTime->now( time_zone => $wf->time_zone() ),
358             time_zone => $wf->time_zone(),
359             }
360             )
361             );
362 21         300 $self->log->info( "Created history object ok" );
363              
364 21         6302 $self->_commit_transaction($wf);
365              
366 21         159 my $state = $wf->_get_workflow_state();
367 21 50       154 if ( $state->autorun ) {
368 0         0 my $state_name = $state->state;
369 0         0 $self->log->info( "State '$state_name' marked to be run ",
370             "automatically; executing that state/action..." );
371 0         0 $wf->_auto_execute_state($state);
372             }
373              
374 21         171 $self->associate_observers_with_workflow($wf);
375 21         120 $wf->notify_observers('create');
376              
377 21         307 return $wf;
378             }
379              
380             sub fetch_workflow {
381 8     8 1 39100 my ( $self, $wf_type, $wf_id, $context, $wf_class ) = @_;
382 8         27 my $wf_config = $self->_get_workflow_config($wf_type);
383              
384 8 50       32 unless ($wf_config) {
385 0         0 workflow_error "No workflow of type '$wf_type' available";
386             }
387 8         32 my $persister = $self->get_persister( $wf_config->{persister} );
388 8         47 my $wf_info = $persister->fetch_workflow($wf_id);
389 8 50 100     3068 $wf_class = $wf_config->{class} || 'Workflow' unless ($wf_class);
390              
391 8 100       32 return unless ($wf_info);
392              
393 6   50     27 $wf_info->{last_update} ||= '';
394 6         50 $self->log->debug(
395             "Fetched data for workflow '$wf_id' ok: ",
396             "[State: $wf_info->{state}] ",
397             "[Last update: $wf_info->{last_update}]"
398             );
399             my $wf = $wf_class->new( $wf_id, $wf_info->{state}, $wf_config,
400 6         2435 $self->{_workflow_state}{$wf_type}, $self );
401              
402 6   66     39 $wf->context( $wf_info->{context} || Workflow::Context->new ); #if ( not $wf->context() );
403 6         21 $wf->last_update( $wf_info->{last_update} );
404              
405 6         64 $persister->fetch_extra_workflow_data($wf);
406              
407 6         48 $self->associate_observers_with_workflow($wf);
408 6         20 $wf->notify_observers('fetch');
409              
410 6         71 return $wf;
411             }
412              
413             sub associate_observers_with_workflow {
414 27     27 1 73 my ( $self, $wf ) = @_;
415 27         117 my $observers = $self->{_workflow_observers}{ $wf->type };
416 27 100       433 return unless ( ref $observers eq 'ARRAY' );
417 5         8 $wf->add_observer($_) for ( @{$observers} );
  5         23  
418             }
419              
420             sub _initialize_workflow_config {
421 3     3   4 my $self = shift;
422 3         4 my $wf_type = shift;
423              
424 3 100       10 if ( ref( $self->config_callback ) eq 'CODE' ) {
425 2         33 my $args = &{ $self->config_callback }($wf_type);
  2         19  
426 2 100 66     56 $self->add_config_from_file( %{$args} ) if $args && %{$args};
  1         7  
  2         10  
427             }
428             }
429              
430             sub _get_workflow_config {
431 163     163   1811 my ( $self, $wf_type ) = @_;
432             $self->_initialize_workflow_config($wf_type)
433 163 100       591 unless $self->{_workflow_config}{$wf_type};
434 163         413 return $self->{_workflow_config}{$wf_type};
435             }
436              
437             sub _insert_workflow {
438 0     0   0 my ( $self, $wf ) = @_;
439 0         0 my $wf_config = $self->_get_workflow_config( $wf->type );
440 0         0 my $persister = $self->get_persister( $wf_config->{persister} );
441 0         0 my $id = $persister->create_workflow($wf);
442 0         0 $wf->id($id);
443 0         0 return $wf;
444              
445             }
446              
447             sub save_workflow {
448 54     54 1 717 my ( $self, $wf ) = @_;
449              
450 54         148 my $old_update = $wf->last_update;
451 54         623 $wf->last_update( DateTime->now( time_zone => $wf->time_zone() ) );
452              
453 54         226 my $wf_config = $self->_get_workflow_config( $wf->type );
454 54         202 my $persister = $self->get_persister( $wf_config->{persister} );
455              
456 54         124 local $EVAL_ERROR = undef;
457 54         96 eval {
458 54         306 $persister->update_workflow($wf);
459 54         8934 $self->log->info( "Workflow '", $wf->id, "' updated ok" );
460 54         8007 my @unsaved = $wf->get_unsaved_history;
461 54         135 foreach my $h (@unsaved) {
462 9         38 $h->set_new_state( $wf->state );
463             }
464 54         377 $persister->create_history( $wf, @unsaved );
465 54         173 $self->log->info( "Created necessary history objects ok" );
466             };
467 54 50       7196 if ($EVAL_ERROR) {
468 0         0 $wf->last_update($old_update);
469 0         0 croak $EVAL_ERROR;
470             }
471              
472 54         205 $wf->notify_observers('save');
473              
474 54         824 return $wf;
475             }
476              
477             # Only implemented for DBI. Don't know if this could be implemented
478             # for other persisters.
479             sub _commit_transaction {
480 74     74   610 my ( $self, $wf ) = @_;
481              
482 74         205 my $wf_config = $self->_get_workflow_config( $wf->type );
483 74         251 my $persister = $self->get_persister( $wf_config->{persister} );
484 74         297 $persister->commit_transaction();
485 74         852 $self->log->debug('Committed transaction.');
486 74         13658 return;
487             }
488              
489             sub _rollback_transaction {
490 0     0   0 my ( $self, $wf ) = @_;
491              
492 0         0 my $wf_config = $self->_get_workflow_config( $wf->type );
493 0         0 my $persister = $self->get_persister( $wf_config->{persister} );
494 0         0 $persister->rollback_transaction();
495 0         0 $self->log->debug('Rolled back transaction.');
496 0         0 return;
497             }
498              
499             sub get_workflow_history {
500 4     4 1 70 my ( $self, $wf ) = @_;
501              
502 4         16 $self->log->debug( "Trying to fetch history for workflow ", $wf->id );
503 4         1508 my $wf_config = $self->_get_workflow_config( $wf->type );
504 4         31 my $persister = $self->get_persister( $wf_config->{persister} );
505 4         26 return $persister->fetch_history($wf);
506             }
507              
508             ########################################
509             # ACTIONS
510              
511             sub _add_action_config {
512 52     52   181 my ( $self, @all_action_config ) = @_;
513              
514 52 100       198 return unless ( scalar @all_action_config );
515              
516 51         138 foreach my $actions (@all_action_config) {
517 51 100       217 next unless ( ref $actions eq 'HASH' );
518              
519             # TODO Handle optional type.
520             # Should we check here to see if this matches an existing
521             # workflow type? Maybe do a type check at the end of the config
522             # process?
523 33 100       149 my $type = exists $actions->{type} ? $actions->{type} : 'default';
524              
525 33         66 my $action;
526 33 100       115 if ( exists $actions->{action} ) {
527 31         77 $action = $actions->{action};
528             } else {
529 2         4 push @{$action}, $actions;
  2         5  
530             }
531              
532 33         60 foreach my $action_config ( @{$action} ) {
  33         84  
533 139         296 my $name = $action_config->{name};
534 139         428 $self->log->debug(
535             "Adding configuration for type '$type', action '$name'");
536 139         41924 $self->{_action_config}{$type}{$name} = $action_config;
537 139         423 my $action_class = $action_config->{class};
538 139 50       509 unless ($action_class) {
539 0         0 configuration_error
540             "Action '$name' must be associated with a ",
541             "class using the 'class' attribute.";
542             }
543             $self->log->debug(
544 139         416 "Trying to include action class '$action_class'...");
545              
546 139         41116 local $EVAL_ERROR = undef;
547 139         7684 eval "require $action_class";
548 139 50       23045 if ($EVAL_ERROR) {
549 0         0 my $msg = $EVAL_ERROR;
550 0         0 $msg =~ s/\\n/ /g;
551 0         0 configuration_error
552             "Cannot include action class '$action_class': $msg";
553             }
554             $self->log->debug(
555 139         493 "Included action '$name' class '$action_class' ok");
556 139 100       42784 if ($self->_validate_action_config) {
557 3         9 my $validate_name = $action_class . '::validate_config';
558 3 100       19 if (exists &$validate_name) {
559 20     20   298 no strict 'refs';
  20         47  
  20         36155  
560 2         8 $self->log->debug(
561             "Validating configuration for action '$name'");
562 2         700 $validate_name->($action_config);
563             }
564             }
565             } # End action for.
566             }
567             }
568              
569             sub get_action_config {
570 60     60 1 819 my ( $self, $wf, $action_name ) = @_;
571 60         175 my $config = $self->{_action_config}{ $wf->type }{$action_name};
572             $config = $self->{_action_config}{default}{$action_name}
573 60 100 66     832 unless ($config and %{$config});
  5         20  
574              
575 60 50       148 unless ($config) {
576 0         0 workflow_error "No action with name '$action_name' available";
577             }
578 60         135 return $config;
579             }
580              
581             sub get_action {
582 0     0 1 0 my ( $self, $wf, $action_name ) = @_;
583 0         0 my $config = $self->get_action_config( $wf, $action_name );;
584 0         0 my $action_class = $config->{class};
585 0         0 return $action_class->new( $wf, $config );
586             }
587              
588             ########################################
589             # PERSISTERS
590              
591             sub _add_persister_config {
592 41     41   128 my ( $self, @all_persister_config ) = @_;
593              
594 41 100       141 return unless ( scalar @all_persister_config );
595              
596 25         70 foreach my $persister_config (@all_persister_config) {
597 27 100       478 next unless ( ref $persister_config eq 'HASH' );
598 16         51 my $name = $persister_config->{name};
599 16         84 $self->log->debug( "Adding configuration for persister '$name'" );
600 16         5126 $self->{_persister_config}{$name} = $persister_config;
601 16         55 my $persister_class = $persister_config->{class};
602 16 50       63 unless ($persister_class) {
603 0         0 configuration_error "You must specify a 'class' in persister ",
604             "'$name' configuration";
605             }
606             $self->log->debug(
607 16         75 "Trying to include persister class '$persister_class'...");
608              
609 16         4422 local $EVAL_ERROR = undef;
610 16         1306 eval "require $persister_class";
611 16 50       148 if ($EVAL_ERROR) {
612 0         0 configuration_error "Cannot include persister class ",
613             "'$persister_class': $EVAL_ERROR";
614             }
615             $self->log->debug(
616 16         99 "Included persister '$name' class '$persister_class' ",
617             "ok; now try to instantiate persister..." );
618              
619             # $EVAL_ERROR already localized above
620 16         6479 my $persister = eval { $persister_class->new($persister_config) };
  16         212  
621 16 50       119 if ($EVAL_ERROR) {
622 0         0 configuration_error "Failed to create instance of persister ",
623             "'$name' of class '$persister_class': $EVAL_ERROR";
624             }
625 16         72 $self->{_persister}{$name} = $persister;
626 16         70 $self->log->debug( "Instantiated persister '$name' ok" );
627             }
628             }
629              
630             sub get_persister {
631 188     188 1 1884 my ( $self, $persister_name ) = @_;
632 188         413 my $persister = $self->{_persister}{$persister_name};
633 188 50       416 unless ($persister) {
634 0         0 workflow_error "No persister with name '$persister_name' available";
635             }
636 188         349 return $persister;
637             }
638              
639             sub get_persisters {
640 0     0 1 0 my $self = shift;
641 0         0 my @persisters = sort keys %{ $self->{_persister} };
  0         0  
642              
643 0         0 return @persisters;
644             }
645              
646             sub get_persister_for_workflow_type {
647 0     0 1 0 my $self = shift;
648              
649 0         0 my ($type) = @_;
650 0         0 my $wf_config = $self->_get_workflow_config($type);
651 0 0       0 if ( not $wf_config ) {
652 0         0 workflow_error "no workflow of type '$type' available";
653             }
654 0         0 my $persister = $self->get_persister( $wf_config->{'persister'} );
655              
656 0         0 return $persister;
657             }
658              
659             ########################################
660             # CONDITIONS
661              
662             sub _add_condition_config {
663 51     51   161 my ( $self, @all_condition_config ) = @_;
664              
665 51 100       202 return unless ( scalar @all_condition_config );
666              
667 50         173 foreach my $conditions (@all_condition_config) {
668 51 100       594 next unless ( ref $conditions eq 'HASH' );
669              
670             my $type
671 27 100       123 = exists $conditions->{type} ? $conditions->{type} : 'default';
672              
673 27         42 my $c;
674 27 100       93 if ( exists $conditions->{condition} ) {
675 25         63 $c = $conditions->{condition};
676             } else {
677 2         3 push @{$c}, $conditions;
  2         4  
678             }
679              
680 27         45 foreach my $condition_config ( @{$c} ) {
  27         69  
681 41         871 my $name = $condition_config->{name};
682 41         158 $self->log->debug( "Adding configuration for condition '$name'" );
683 41         10399 $self->{_condition_config}{$type}{$name} = $condition_config;
684 41         110 my $condition_class = $condition_config->{class};
685 41 50       111 unless ($condition_class) {
686 0         0 configuration_error "Condition '$name' must be associated ",
687             "with a class using the 'class' attribute";
688             }
689             $self->log->debug(
690 41         131 "Trying to include condition class '$condition_class'");
691              
692 41         9673 local $EVAL_ERROR = undef;
693 41         2675 eval "require $condition_class";
694 41 50       7272 if ($EVAL_ERROR) {
695 0         0 configuration_error "Cannot include condition class ",
696             "'$condition_class': $EVAL_ERROR";
697             }
698             $self->log->debug(
699 41         159 "Included condition '$name' class '$condition_class' ",
700             "ok; now try to instantiate condition..." );
701              
702             # $EVAL_ERROR already localized above
703 41         10273 my $condition = eval { $condition_class->new($condition_config) };
  41         408  
704 41 50       130 if ($EVAL_ERROR) {
705 0         0 configuration_error
706             "Cannot create condition '$name': $EVAL_ERROR";
707             }
708 41         151 $self->{_conditions}{$type}{$name} = $condition;
709 41         119 $self->log->debug( "Instantiated condition '$name' ok" );
710             }
711             }
712             }
713              
714             sub get_condition {
715 232     232 1 4378 my ( $self, $name, $type ) = @_;
716              
717 232         281 my $condition;
718              
719 232 100       477 if ( defined $type ) {
720 231         639 $condition = $self->{_conditions}{$type}{$name};
721             }
722              
723             # This catches cases where type isn't defined and cases
724             # where the condition was defined as the default rather than
725             # the current Workflow type.
726 232 100       443 if ( not defined $condition ) {
727 175         423 $condition = $self->{_conditions}{'default'}{$name};
728             }
729              
730 232 100 66     641 if ( not defined $condition
731             and $name =~ m/ \A ! /msx ) {
732 13         26 my $negated = $name;
733 13         55 $negated =~ s/ \A ! //gx;
734              
735 13 50       76 if ( $self->get_condition( $negated, $type ) ) {
736 13         112 $condition = Workflow::Condition::Negated->new(
737             { name => $name }
738             );
739              
740 13 50       77 $type = 'default' unless defined $type;
741 13         44 $self->{_conditions}{$type}{$name} = $condition;
742             }
743             }
744              
745 232 50       454 unless ($condition) {
746 0         0 workflow_error "No condition with name '$name' available";
747             }
748 232         522 return $condition;
749             }
750              
751             ########################################
752             # VALIDATORS
753              
754             sub _add_validator_config {
755 42     42   153 my ( $self, @all_validator_config ) = @_;
756              
757 42 100       144 return unless (@all_validator_config);
758              
759 37         104 foreach my $validators (@all_validator_config) {
760 37 100       186 next unless ( ref $validators eq 'HASH' );
761              
762 13         24 my $v;
763 13 100       59 if ( exists $validators->{validator} ) {
764 12         41 $v = $validators->{validator};
765             } else {
766 1         1 push @{$v}, $validators;
  1         3  
767             }
768              
769 13         29 for my $validator_config ( @{$v} ) {
  13         41  
770 13         37 my $name = $validator_config->{name};
771 13         56 $self->log->debug( "Adding configuration for validator '$name'" );
772 13         4514 $self->{_validator_config}{$name} = $validator_config;
773 13         51 my $validator_class = $validator_config->{class};
774 13 50       58 unless ($validator_class) {
775 0         0 configuration_error
776             "Validator '$name' must be associated with ",
777             "a class using the 'class' attribute.";
778             }
779             $self->log->debug(
780 13         57 "Trying to include validator class '$validator_class'");
781              
782 13         4567 local $EVAL_ERROR = undef;
783 13         827 eval "require $validator_class";
784 13 50       105 if ($EVAL_ERROR) {
785 0         0 workflow_error
786             "Cannot include validator class '$validator_class': $EVAL_ERROR";
787             }
788             $self->log->debug(
789 13         98 "Included validator '$name' class '$validator_class' ",
790             " ok; now try to instantiate validator..."
791             );
792              
793             # $EVAL_ERROR already localized above
794 13         6588 my $validator = eval { $validator_class->new($validator_config) };
  13         133  
795 13 50       71 if ($EVAL_ERROR) {
796 0         0 workflow_error "Cannot create validator '$name': $EVAL_ERROR";
797             }
798 13         156 $self->{_validators}{$name} = $validator;
799 13         59 $self->log->debug( "Instantiated validator '$name' ok" );
800             }
801             }
802             }
803              
804             sub get_validator {
805 13     13 1 198 my ( $self, $name ) = @_;
806 13 50       63 unless ( $self->{_validators}{$name} ) {
807 0         0 workflow_error "No validator with name '$name' available";
808             }
809 13         41 return $self->{_validators}{$name};
810             }
811              
812             sub get_validators {
813 0     0 1 0 my $self = shift;
814 0         0 my @validators = sort keys %{ $self->{_validators} };
  0         0  
815 0         0 return @validators;
816             }
817              
818             sub _validate_action_config {
819 139     139   635 return $VALIDATE_ACTION_CONFIG;
820             }
821              
822             1;
823              
824             __END__
825              
826             =pod
827              
828             =head1 NAME
829              
830             Workflow::Factory - Generates new workflow and supporting objects
831              
832             =head1 VERSION
833              
834             This documentation describes version 1.62 of this package
835              
836             =head1 SYNOPSIS
837              
838             # Import the singleton for easy access
839             use Workflow::Factory qw( FACTORY );
840              
841             # Add XML configurations to the factory
842             FACTORY->add_config_from_file( workflow => 'workflow.xml',
843             action => [ 'myactions.xml', 'otheractions.xml' ],
844             validator => [ 'validator.xml', 'myvalidators.xml' ],
845             condition => 'condition.xml',
846             persister => 'persister.xml' );
847              
848             # Create a new workflow of type 'MyWorkflow'
849             my $wf = FACTORY->create_workflow( 'MyWorkflow' );
850              
851             # Fetch an existing workflow with ID '25'
852             my $wf = FACTORY->fetch_workflow( 'MyWorkflow', 25 );
853              
854             =head1 DESCRIPTION
855              
856             =head2 Public
857              
858             The Workflow Factory is your primary interface to the workflow
859             system. You give it the configuration files and/or data structures for
860             the L<Workflow>, L<Workflow::Action>, L<Workflow::Condition>,
861             L<Workflow::Persister>, and L<Workflow::Validator> objects and then
862             you ask it for new and existing L<Workflow> objects.
863              
864             =head2 Internal
865              
866             Developers using the workflow system should be familiar with how the
867             factory processes configurations and how it makes the various
868             components of the system are instantiated and stored in the factory.
869              
870             =head1 METHODS
871              
872             =head2 Public Methods
873              
874             =head3 instance()
875              
876             The factory is a singleton, this is how you get access to the
877             instance. You can also just import the 'FACTORY' constant as in the
878             L</SYNOPSIS>.
879              
880             =head3 create_workflow( $workflow_type, $context, $wf_class )
881              
882             Create a new workflow of type C<$workflow_type>. This will create a
883             new record in whatever persistence mechanism you have associated with
884             C<$workflow_type> and set the workflow to its initial state.
885              
886             The C<$context> argument is optional, you can pass an exisiting instance
887             of Workflow::Context to be reused. Otherwise a new instance is created.
888              
889             The C<$wf_class> argument is optional. Pass it the name of a class to be
890             used for the workflow to be created. By default, all workflows are of the
891             I<Workflow> class.
892              
893             Any observers you've associated with this workflow type will be
894             attached to the returned workflow object.
895              
896             This fires a 'create' event from the just-created workflow object. See
897             C<WORKFLOWS ARE OBSERVABLE> in L<Workflow> for more.
898              
899             Returns: newly created workflow object.
900              
901             =head3 fetch_workflow( $workflow_type, $workflow_id, $context, $wf_class )
902              
903             Retrieve a workflow object of type C<$workflow_type> and ID
904             C<$workflow_id>. (The C<$workflow_type> is necessary so we can fetch
905             the workflow using the correct persister.) If a workflow with ID
906             C<$workflow_id> is not found C<undef> is returned.
907              
908             The C<$context> argument is optional, you can pass an exisiting instance
909             of Workflow::Context to be reused. Otherwise a new instance is created.
910              
911             The C<$wf_class> argument is optional. Pass it the name of a class to be
912             used for the workflow to be created. By default, all workflows are of the
913             I<Workflow> class.
914              
915             Any observers you've associated with this workflow type will be
916             attached to the returned workflow object.
917              
918             This fires a 'fetch' event from the retrieved workflow object. See
919             C<WORKFLOWS ARE OBSERVABLE> in L<Workflow> for more.
920              
921             Throws exception if no workflow type C<$workflow_type> available.
922              
923             Returns: L<Workflow> object
924              
925             =head3 add_config_from_file( %config_declarations )
926              
927             Pass in filenames for the various components you wish to initialize
928             using the keys 'action', 'condition', 'persister', 'validator' and
929             'workflow'. The value for each can be a single filename or an arrayref
930             of filenames.
931              
932             The system is familiar with the 'perl' and 'xml' configuration formats
933             -- see the 'doc/configuration.txt' for what we expect as the format
934             and will autodetect the types based on the file extension of each
935             file. Just give your file the right extension and it will be read in
936             properly.
937              
938             You may also use your own custom configuration file format -- see
939             C<SUBCLASSING> in L<Workflow::Config> for what you need to do.
940              
941             You can also read it in yourself and add the resulting hash reference
942             directly to the factory using C<add_config()>. However, you need to
943             ensure the configurations are added in the proper order -- when you
944             add an 'action' configuration and reference 'validator' objects, those
945             objects should already be read in. A good order is: 'validator',
946             'condition', 'action', 'workflow'. Then just pass the resulting hash
947             references to C<add_config()> using the right type and the behavior
948             should be exactly the same.
949              
950             Returns: nothing; if we run into a problem parsing one of the files or
951             creating the objects it requires we throw a L<Workflow::Exception>.
952              
953             =head3 add_config( %config_hashrefs )
954              
955             Similar to C<add_config_from_file()> -- the keys may be 'action',
956             'condition', 'persister', 'validator' and/or 'workflow'. But the
957             values are the actual configuration hashrefs instead of the files
958             holding the configurations.
959              
960             You normally will only need to call this if you are programmatically
961             creating configurations (e.g., hot-deploying a validator class
962             specified by a user) or using a custom configuration format and for
963             some reason do not want to use the built-in mechanism in
964             L<Workflow::Config> to read it for you.
965              
966             Returns: nothing; if we encounter an error trying to create the
967             objects referenced in a configuration we throw a
968             L<Workflow::Exception>.
969              
970             =head3 get_persister_for_workflow_type
971              
972             =head3 get_persisters
973              
974             #TODO
975              
976             =head3 get_validators
977              
978             #TODO
979              
980             =head2 Internal Methods
981              
982             #TODO
983              
984             =head3 save_workflow( $workflow )
985              
986             Stores the state and current datetime of the C<$workflow> object. This
987             is normally called only from the L<Workflow> C<execute_action()>
988             method.
989              
990             This method respects transactions if the selected persister supports it.
991             Currently, the DBI-based persisters will commit the workflow transaction
992             if everything executes successfully and roll back if something fails.
993             Note that you need to manage any L<Workflow::Persister::DBI::ExtraData>
994             transactions yourself.
995              
996             Returns: C<$workflow>
997              
998             =head3 get_workflow_history( $workflow )
999              
1000             Retrieves all L<Workflow::History> objects related to C<$workflow>.
1001              
1002             B<NOTE>: Normal users get the history objects from the L<Workflow>
1003             object itself. Under the covers it calls this.
1004              
1005             Returns: list of L<Workflow::History> objects
1006              
1007             =head3 get_action( $workflow, $action_name ) [ deprecated ]
1008              
1009             Retrieves the action C<$action_name> from workflow C<$workflow>. Note
1010             that this does not do any checking as to whether the action is proper
1011             given the state of C<$workflow> or anything like that. It is mostly an
1012             internal method for L<Workflow> (which B<does> do checking as to the
1013             propriety of the action) to instantiate new actions.
1014              
1015             Throws exception if no action with name C<$action_name> available.
1016              
1017             =head3 get_action_config( $workflow, $action_name )
1018              
1019             Retrieves the configuration for action C<$action_name> as specified in
1020             the actions configuration file, with the keys listed in
1021             L<the 'action' section of Workflow::Config|Workflow::Config/"action">
1022              
1023             Throws exception if no action with name C<$action_name> available.
1024              
1025             Returns: A hash with the configuration as its keys.
1026              
1027             =head3 get_persister( $persister_name )
1028              
1029             Retrieves the persister with name C<$persister_name>.
1030              
1031             Throws exception if no persister with name C<$persister_name>
1032             available.
1033              
1034             =head3 get_condition( $condition_name )
1035              
1036             Retrieves the condition with name C<$condition_name>.
1037              
1038             Throws exception if no condition with name C<$condition_name>
1039             available.
1040              
1041             =head3 get_validator( $validator_name )
1042              
1043             Retrieves the validator with name C<$validator_name>.
1044              
1045             Throws exception if no validator with name C<$validator_name>
1046             available.
1047              
1048             =head2 Internal Configuration Methods
1049              
1050             =head3 _add_workflow_config( @config_hashrefs )
1051              
1052             Adds all configurations in C<@config_hashrefs> to the factory. Also
1053             cycles through the workflow states and creates a L<Workflow::State>
1054             object for each. These states are passed to the workflow when it is
1055             instantiated.
1056              
1057             We also require any necessary observer classes and throw an exception
1058             if we cannot. If successful the observers are kept around and attached
1059             to a workflow in L<create_workflow()|/create_workflow> and
1060             L<fetch_workflow()|/fetch_workflow>.
1061              
1062             Returns: nothing
1063              
1064             =head3 _load_observers( $workflow_config_hashref )
1065              
1066             Loads and adds observers based on workflow type
1067              
1068             Returns number indicating amount of observers added, meaning zero can indicate success based on expected outcome.
1069              
1070             =head3 _add_action_config( @config_hashrefs )
1071              
1072             Adds all configurations in C<@config_hashrefs> to the factory, doing a
1073             'require' on the class referenced in the 'class' attribute of each
1074             action.
1075              
1076             Throws an exception if there is no 'class' associated with an action
1077             or if we cannot 'require' that class.
1078              
1079             Returns: nothing
1080              
1081             =head3 _add_persister_config( @config_hashrefs )
1082              
1083             Adds all configurations in C<@config_hashrefs> to the factory, doing a
1084             'require' on the class referenced in the 'class' attribute of each
1085             persister.
1086              
1087             Throws an exception if there is no 'class' associated with a
1088             persister, if we cannot 'require' that class, or if we cannot
1089             instantiate an object of that class.
1090              
1091             Returns: nothing
1092              
1093             =head3 _add_condition_config( @config_hashrefs )
1094              
1095             Adds all configurations in C<@config_hashrefs> to the factory, doing a
1096             'require' on the class referenced in the 'class' attribute of each
1097             condition.
1098              
1099             Throws an exception if there is no 'class' associated with a
1100             condition, if we cannot 'require' that class, or if we cannot
1101             instantiate an object of that class.
1102              
1103             Returns: nothing
1104              
1105             =head3 _add_validator_config( @config_hashrefs )
1106              
1107             Adds all configurations in C<@config_hashrefs> to the factory, doing a
1108             'require' on the class referenced in the 'class' attribute of each
1109             validator.
1110              
1111             Throws an exception if there is no 'class' associated with a
1112             validator, if we cannot 'require' that class, or if we cannot
1113             instantiate an object of that class.
1114              
1115             Returns: nothing
1116              
1117             =head3 _commit_transaction
1118              
1119             Calls the commit method in the workflow's persister.
1120              
1121             Returns: nothing
1122              
1123             =head3 _rollback_transaction
1124              
1125             Calls the rollback method in the workflow's persister.
1126              
1127             =head3 associate_observers_with_workflow
1128              
1129             Add defined observers with workflow.
1130              
1131             The workflow has to be provided as the single parameter accepted by this
1132             method.
1133              
1134             The observers added will have to be of the type relevant to the workflow type.
1135              
1136             =head3 new
1137              
1138             The new method is a dummy constructor, since we are using a factory it makes
1139             no sense to call new - and calling new will result in a L<Workflow::Exception>
1140              
1141             L</instance> should be called or the imported 'FACTORY' should be utilized.
1142              
1143             =head1 DYNAMIC CONFIG LOADING
1144              
1145             If you have either a large set of config files or a set of very large
1146             config files then you may not want to incur the overhead of loading
1147             each and every one on startup if you cannot predict which set you will
1148             use in that instance of your application.
1149              
1150             This approach doesn't make much sense in a persistent environment such
1151             as mod_perl but it may lower startup costs if you have regularly
1152             scheduled scripts that may not need to touch all possible types of
1153             workflow.
1154              
1155             To do this you can specify a callback that the factory will use to
1156             retrieve batched hashes of config declarations. Whenever an unknown
1157             workflow name is encountered the factory will first try to load your
1158             config declarations then continue.
1159              
1160             The callback takes one argument which is the workflow type. It should
1161             return a reference to a hash of arguments in a form suitable for
1162             C<add_config_from_file>.
1163              
1164             For example:
1165              
1166             use Workflow::Factory qw(FACTORY);
1167             use My::Config::System;
1168              
1169             sub init {
1170             my $self = shift;
1171              
1172             FACTORY->config_callback(
1173             sub {
1174             my $wf_type = shift;
1175             my %ret = My::Config::System->get_files_for_wf( $wf_type ) || ();
1176             return \%ret;
1177             }
1178             );
1179             }
1180              
1181             =head1 SUBCLASSING
1182              
1183             =head2 Implementation and Usage
1184              
1185             You can subclass the factory to implement your own methods and still
1186             use the useful facade of the C<FACTORY> constant. For instance, the
1187             implementation is typical Perl subclassing:
1188              
1189             package My::Cool::Factory;
1190              
1191             use strict;
1192             use base qw( Workflow::Factory );
1193              
1194             sub some_cool_method {
1195             my ( $self ) = @_;
1196             ...
1197             }
1198              
1199             To use your factory you can just do the typical import:
1200              
1201             #!/usr/bin/perl
1202              
1203             use strict;
1204             use My::Cool::Factory qw( FACTORY );
1205              
1206             Or you can call C<instance()> directly:
1207              
1208             #!/usr/bin/perl
1209              
1210             use strict;
1211             use My::Cool::Factory;
1212              
1213             my $factory = My::Cool::Factory->instance();
1214              
1215             =head1 GLOBAL RUN-TIME OPTIONS
1216              
1217             Setting package variable B<$VALIDATE_ACTION_CONFIG> to a true value (it
1218             is undef by default) turns on optional validation of extra attributes
1219             of L<Workflow::Action> configs. See L<Workflow::Action> for details.
1220              
1221             =head1 SEE ALSO
1222              
1223             =over
1224              
1225             =item * L<Workflow>
1226              
1227             =item * L<Workflow::Action>
1228              
1229             =item * L<Workflow::Condition>
1230              
1231             =item * L<Workflow::Config>
1232              
1233             =item * L<Workflow::Persister>
1234              
1235             =item * L<Workflow::Validator>
1236              
1237             =back
1238              
1239             =head1 COPYRIGHT
1240              
1241             Copyright (c) 2003-2023 Chris Winters. All rights reserved.
1242              
1243             This library is free software; you can redistribute it and/or modify
1244             it under the same terms as Perl itself.
1245              
1246             Please see the F<LICENSE>
1247              
1248             =head1 AUTHORS
1249              
1250             Please see L<Workflow>
1251              
1252             =cut