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