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