1package Workflow;
2
3use warnings;
4use strict;
5use v5.14.0; # warnings
6use base qw( Workflow::Base );
7use Log::Log4perl qw( get_logger );
8use Workflow::Context;
9use Workflow::Exception qw( workflow_error );
10use Exception::Class;
11use Workflow::Factory qw( FACTORY );
12use Carp qw(croak carp);
13use English qw( -no_match_vars );
14
15my @FIELDS   = qw( id type description state last_update time_zone );
16my @INTERNAL = qw( _factory _observers );
17__PACKAGE__->mk_accessors( @FIELDS, @INTERNAL );
18
19$Workflow::VERSION = '1.59';
20
21use constant NO_CHANGE_VALUE => 'NOCHANGE';
22
23########################################
24# INTERNAL METHODS
25
26sub add_observer {
27    my ($self, @observers) = @_;
28
29    if (not $self->_observers) {
30        $self->_observers( [] );
31    }
32    push @{$self->_observers}, @observers;
33
34    return;
35}
36
37sub notify_observers {
38    my ($self, @args) = @_;
39
40    return unless $self->_observers;
41    $_->($self, @args) for @{$self->_observers};
42
43    return;
44}
45
46########################################
47# PUBLIC METHODS
48
49# this is our only read-write property...
50
51sub context {
52    my ( $self, $context ) = @_;
53    if ($context) {
54
55        # We already have a context, merge the new one with ours; (the
56        # new one wins with dupes)
57
58        if ( $self->{context} ) {
59            $self->{context}->merge($context);
60        } else {
61            $context->param( workflow_id => $self->id );
62            $self->{context} = $context;
63        }
64    }
65    unless ( $self->{context} ) {
66        $self->{context} = Workflow::Context->new();
67    }
68    return $self->{context};
69}
70
71sub get_current_actions {
72    my ( $self, $group ) = @_;
73    $self->log->debug( "Getting current actions for wf '", $self->id, "'" );
74    my $wf_state = $self->_get_workflow_state;
75    return $wf_state->get_available_action_names( $self, $group );
76}
77
78sub get_action {
79    my ( $self, $action_name ) = @_;
80
81    my $state = $self->state;
82    $self->log->debug(
83        "Trying to find action '$action_name' in state '$state'");
84
85    my $wf_state = $self->_get_workflow_state;
86    unless ( $wf_state->contains_action($action_name) ) {
87        workflow_error
88            "State '$state' does not contain action '$action_name'";
89    }
90    $self->log->debug("Action '$action_name' exists in state '$state'");
91
92    my $action = $self->_get_workflow_state()->get_action( $self, $action_name );
93
94    # This will throw an exception which we want to bubble up
95    $wf_state->evaluate_action( $self, $action_name );
96    return $action;
97}
98
99sub get_action_fields {
100    my ( $self, $action_name ) = @_;
101    my $action = $self->get_action($action_name);
102    return $action->fields;
103}
104
105sub execute_action {
106    my ( $self, $action_name, $autorun ) = @_;
107
108    # This checks the conditions behind the scenes, so there's no
109    # explicit 'check conditions' step here
110
111    my $action = $self->get_action($action_name);
112
113    # Need this in case we encounter an exception after we store the
114    # new state
115
116    my $old_state = $self->state;
117    my ( $new_state, $action_return );
118
119    eval {
120        $action->validate($self);
121        $self->log->debug("Action validated ok");
122        $action_return = $action->execute($self);
123        $self->log->debug("Action executed ok");
124
125        $new_state = $self->_get_next_state( $action_name, $action_return );
126        if ( $new_state ne NO_CHANGE_VALUE ) {
127            $self->log->info("Set new state '$new_state' after action executed");
128            $self->state($new_state);
129        }
130
131        # this will save the workflow histories as well as modify the
132        # state of the workflow history to reflect the NEW state of
133        # the workflow; if it fails we should have some means for the
134        # factory to rollback other transactions...
135
136        # Update
137        # Jim Brandt 4/16/2008: Implemented transactions for DBI persisters.
138        # Implementation still depends on each persister.
139
140        $self->_factory()->save_workflow($self);
141
142        # If using a DBI persister with no autocommit, commit here.
143        $self->_factory()->_commit_transaction($self);
144
145        $self->log->info("Saved workflow with possible new state ok");
146    };
147
148    # If there's an exception, reset the state to the original one and
149    # rethrow
150
151    if ($EVAL_ERROR) {
152        my $error = $EVAL_ERROR;
153        $self->log->error(
154            "Caught exception from action: $error; reset ",
155            "workflow to old state '$old_state'"
156        );
157        $self->state($old_state);
158
159        $self->_factory()->_rollback_transaction($self);
160
161        # If it is a validation error we rethrow it so it can be evaluated
162        # by the caller to provide better feedback to the user
163        if (Exception::Class->caught('Workflow::Exception::Validation')) {
164            $EVAL_ERROR->rethrow();
165        }
166
167        # Don't use 'workflow_error' here since $error should already
168        # be a Workflow::Exception object or subclass
169
170        croak $error;
171    }
172
173    # clear condition cache on state change
174    delete $self->{'_condition_result_cache'};
175    $self->notify_observers( 'execute', $old_state, $action_name, $autorun );
176
177    my $new_state_obj = $self->_get_workflow_state;
178    if ( $old_state ne $new_state ) {
179        $self->notify_observers( 'state change', $old_state, $action_name,
180            $autorun );
181    }
182
183    if ( $new_state_obj->autorun ) {
184        $self->log->info(
185            "State '$new_state' marked to be run ",
186            "automatically; executing that state/action..."
187            );
188        $self->_auto_execute_state($new_state_obj);
189    }
190    return $self->state;
191}
192
193sub add_history {
194    my ( $self, @items ) = @_;
195
196    my @to_add = ();
197    foreach my $item (@items) {
198        if ( ref $item eq 'HASH' ) {
199            $item->{workflow_id} = $self->id;
200            $item->{time_zone}   = $self->time_zone();
201            push @to_add, Workflow::History->new($item);
202            $self->log->debug("Adding history from hashref");
203        } elsif ( ref $item and $item->isa('Workflow::History') ) {
204            $item->workflow_id( $self->id );
205            push @to_add, $item;
206            $self->log->debug("Adding history object directly");
207        } else {
208            workflow_error "I don't know how to add a history of ", "type '",
209                ref($item), "'";
210        }
211
212        if ($EVAL_ERROR) {
213            workflow_error "Unable to assert history object";
214        }
215    }
216    push @{ $self->{_histories} }, @to_add;
217    $self->notify_observers( 'add history', \@to_add );
218}
219
220sub get_history {
221    my ($self) = @_;
222    $self->{_histories} ||= [];
223    my @uniq_history = ();
224    my %seen_ids     = ();
225    my @all_history  = (
226        $self->_factory()->get_workflow_history($self),
227        @{ $self->{_histories} }
228    );
229    foreach my $history (@all_history) {
230        my $id = $history->id;
231        if ($id) {
232            unless ( $seen_ids{$id} ) {
233                push @uniq_history, $history;
234            }
235            $seen_ids{$id}++;
236        } else {
237            push @uniq_history, $history;
238        }
239    }
240    return @uniq_history;
241}
242
243sub get_unsaved_history {
244    my ($self) = @_;
245    return grep { !$_->is_saved } @{ $self->{_histories} };
246}
247
248sub clear_history {
249    my ($self) = @_;
250    $self->{_histories} = [];
251}
252
253########################################
254# PRIVATE METHODS
255
256sub init {
257    my ( $self, $id, $current_state, $config, $wf_state_objects, $factory )
258        = @_;
259    $id      ||= '';
260    $factory ||= FACTORY;
261    $self->log->info(
262        "Instantiating workflow of with ID '$id' and type ",
263        "'$config->{type}' with current state '$current_state'"
264    );
265
266    $self->id($id) if ($id);
267    $self->_factory($factory);
268
269    $self->state($current_state);
270    $self->type( $config->{type} );
271    $self->description( $config->{description} );
272    my $time_zone
273        = exists $config->{time_zone} ? $config->{time_zone} : 'floating';
274    $self->time_zone($time_zone);
275
276    # other properties go into 'param'...
277    while ( my ( $key, $value ) = each %{$config} ) {
278        next if ( $key =~ /^(type|description)$/ );
279        next if ( ref $value );
280        $self->log->debug("Assigning parameter '$key' -> '$value'");
281        $self->param( $key, $value );
282    }
283
284    # Now set all the Workflow::State objects created and cached by the
285    # factory
286
287    foreach my $wf_state ( @{$wf_state_objects} ) {
288        $self->_set_workflow_state($wf_state);
289    }
290}
291
292# Override from Class::Accessor so only certain callers can set
293# properties
294
295sub set {
296    my ( $self, $prop, $value ) = @_;
297    my $calling_pkg = ( caller 1 )[0];
298    unless ( $calling_pkg =~ /^Workflow/ ) {
299        carp "Tried to set from: ", join ', ', caller 1;
300        workflow_error
301            "Don't try to use my private setters from '$calling_pkg'!";
302    }
303    $self->{$prop} = $value;
304}
305
306sub _get_action { # for backward compatibility with 1.49 and before
307    goto &get_action;
308}
309
310sub _get_workflow_state {
311    my ( $self, $state ) = @_;
312    $state ||= '';             # get rid of -w...
313    my $use_state = $state || $self->state;
314    $self->log->debug(
315        "Finding Workflow::State object for state [given: $use_state] ",
316        "[internal: ", $self->state, "]" );
317    my $wf_state = $self->{_states}{$use_state};
318    unless ($wf_state) {
319        workflow_error "No state '$use_state' exists in workflow '",
320            $self->type, "'";
321    }
322    return $wf_state;
323}
324
325sub _set_workflow_state {
326    my ( $self, $wf_state ) = @_;
327    $self->{_states}{ $wf_state->state } = $wf_state;
328}
329
330sub _get_next_state {
331    my ( $self, $action_name, $action_return ) = @_;
332    my $wf_state = $self->_get_workflow_state;
333    return $wf_state->get_next_state( $action_name, $action_return );
334}
335
336sub _auto_execute_state {
337    my ( $self, $wf_state ) = @_;
338    my $action_name;
339    eval { $action_name = $wf_state->get_autorun_action_name($self); };
340    if ($EVAL_ERROR)
341    {    # we found an error, possibly more than one or none action
342            # are available in this state
343        if ( !$wf_state->may_stop() ) {
344
345            # we are in autorun, but stopping is not allowed, so
346            # rethrow
347            my $error = $EVAL_ERROR;
348            $error->rethrow();
349        }
350    } else {    # everything is fine, execute action
351        $self->log->debug(
352            "Found action '$action_name' to execute in ",
353            "autorun state ",
354            $wf_state->state
355            );
356        $self->execute_action( $action_name, 1 );
357    }
358}
359
3601;
361
362__END__
363
364=pod
365
366=begin markdown
367
368[![CPAN version](https://badge.fury.io/pl/Workflow.svg)](http://badge.fury.io/pl/Workflow)
369[![Build Status](https://travis-ci.org/jonasbn/perl-workflow.svg?branch=master)](https://travis-ci.org/jonasbn/perl-workflow)
370[![Coverage Status](https://coveralls.io/repos/github/jonasbn/perl-workflow/badge.svg?branch=master)](https://coveralls.io/github/jonasbn/perl-workflow?branch=master)
371
372=end markdown
373
374=head1 NAME
375
376Workflow - Simple, flexible system to implement workflows
377
378=head1 VERSION
379
380This documentation describes version 1.59 of Workflow
381
382=head1 SYNOPSIS
383
384 use Workflow::Factory qw( FACTORY );
385
386 # Defines a workflow of type 'myworkflow'
387 my $workflow_conf  = 'workflow.xml';
388
389 # contents of 'workflow.xml'
390
391 <workflow>
392     <type>myworkflow</type>
393     <time_zone>local</time_zone>
394     <description>This is my workflow.</description>
395
396     <state name="INITIAL">
397         <action name="upload file" resulting_state="uploaded" />
398     </state>
399     <state name="uploaded" autorun="yes">
400         <action name="verify file" resulting_state="verified file">
401              <!-- everyone other than 'CWINTERS' must verify -->
402              <condition test="$context->{user} ne 'CWINTERS'" />
403         </action>
404         <action name="null" resulting_state="annotated">
405              <condition test="$context->{user} eq 'CWINTERS'" />
406         </action>
407     </state>
408     <state name="verified file">
409         <action name="annotate">
410             <condition name="can_annotate" />
411         </action>
412         <action name="null">
413             <condition name="!can_annotate" />
414         </action>
415     </state>
416     <state name="annotated" autorun="yes" may_stop="yes">
417         <action name="null" resulting_state="finished">
418            <condition name="completed" />
419         </action>
420     </state>
421     <state name="finished" />
422 </workflow>
423
424 # Defines actions available to the workflow
425 my $action_conf    = 'action.xml';
426
427 # contents of 'action.xml'
428
429 <actions>
430     <action name="upload file" class="MyApp::Action::Upload">
431         <field name="path" label="File Path"
432                description="Path to file" is_required="yes" />
433     </action>
434
435     <action name="verify file" class="MyApp::Action::Verify">
436         <validator name="filesize_cap">
437             <arg>$file_size</arg>
438         </validator>
439     </action>
440
441     <action name="annotate"    class="MyApp::Action::Annotate" />
442
443     <action name="null"        class="Workflow::Action::Null" />
444 </actions>
445
446 # Defines conditions available to the workflow
447 my $condition_conf = 'condition.xml';
448
449 # contents of 'condition.xml'
450
451 <conditions>
452     <condition name="can_annotate"
453                class="MyApp::Condition::CanAnnotate" />
454 </conditions>
455
456 # Defines validators available to the actions
457 my $validator_conf = 'validator.xml';
458
459 # contents of 'validator.xml'
460
461 <validators>
462     <validator name="filesize_cap" class="MyApp::Validator::FileSizeCap">
463         <param name="max_size" value="20M" />
464     </validator>
465 </validators>
466
467 # Stock the factory with the configurations; we can add more later if
468 # we want
469 $self->_factory()->add_config_from_file(
470     workflow   => $workflow_conf,
471     action     => $action_conf,
472     condition  => $condition_conf,
473     validator  => $validator_conf
474 );
475
476 # Instantiate a new workflow...
477 my $workflow = $self->_factory()->create_workflow( 'myworkflow' );
478 print "Workflow ", $workflow->id, " ",
479       "currently at state ", $workflow->state, "\n";
480
481 # Display available actions...
482 print "Available actions: ", $workflow->get_current_actions, "\n";
483
484 # Get the data needed for action 'upload file' (assumed to be
485 # available in the current state) and display the fieldname and
486 # description
487
488 print "Action 'upload file' requires the following fields:\n";
489 foreach my $field ( $workflow->get_action_fields( 'FOO' ) ) {
490     print $field->name, ": ", $field->description,
491           "(Required? ", $field->is_required, ")\n";
492 }
493
494 # Add data to the workflow context for the validators, conditions and
495 # actions to work with
496
497 my $context = $workflow->context;
498 $context->param( current_user => $user );
499 $context->param( sections => \@sections );
500 $context->param( path => $path_to_file );
501
502 # Execute one of them
503 $workflow->execute_action( 'upload file' );
504
505 print "New state: ", $workflow->state, "\n";
506
507 # Later.... fetch an existing workflow
508 my $id = get_workflow_id_from_user( ... );
509 my $workflow = $self->_factory()->fetch_workflow( 'myworkflow', $id );
510 print "Current state: ", $workflow->state, "\n";
511
512=head1 QUICK START
513
514The F<eg/ticket/> directory contains a configured workflow system.
515You can access the same data and logic in two ways:
516
517=over
518
519=item * a command-line application (ticket.pl)
520
521=item * a CGI script               (ticket.cgi)
522
523=item * a web application          (ticket_web.pl)
524
525=back
526
527To initialize:
528
529        perl ticket.pl --db
530
531To run the command-line application:
532
533        perl ticket.pl
534
535To access the database and data from CGI, add the relevant
536configuration for your web server and call ticket.cgi:
537
538        http://www.mysite.com/workflow/ticket.cgi
539
540To start up the standalone web server:
541
542        perl ticket_web.pl
543
544(Barring changes to HTTP::Daemon and forking the standalone server
545won't work on Win32; use CGI instead, although patches are always
546welcome.)
547
548For more info, see F<eg/ticket/README>
549
550=head1 DESCRIPTION
551
552=head2 Overview
553
554This is a standalone workflow system. It is designed to fit into your
555system rather than force your system to fit to it. You can save
556workflow information to a database or the filesystem (or a custom
557storage). The different components of a workflow system can be
558included separately as libraries to allow for maximum reusibility.
559
560=head2 User Point of View
561
562As a user you only see two components, plus a third which is really
563embedded into another:
564
565=over 4
566
567=item *
568
569L<Workflow::Factory> - The factory is your interface for creating new
570workflows and fetching existing ones. You also feed all the necessary
571configuration files and/or data structures to the factory to
572initialize it.
573
574=item *
575
576L<Workflow> - When you get the workflow object from the workflow
577factory you can only use it in a few ways -- asking for the current
578state, actions available for the state, data required for a particular
579action, and most importantly, executing a particular action. Executing
580an action is how you change from one state to another.
581
582=item *
583
584L<Workflow::Context> - This is a blackboard for data from your
585application to the workflow system and back again. Each instantiation
586of a L<Workflow> has its own context, and actions executed by the
587workflow can read data from and deposit data into the context.
588
589=back
590
591=head2 Developer Point of View
592
593The workflow system has four basic components:
594
595=over 4
596
597=item *
598
599B<workflow> - The workflow is a collection of states; you define the
600states, how to move from one state to another, and under what
601conditions you can change states.
602
603This is represented by the L<Workflow> object. You normally do not
604need to subclass this object for customization.
605
606=item *
607
608B<action> - The action is defined by you or in a separate library. The
609action is triggered by moving from one state to another and has access
610to the workflow and more importantly its context.
611
612The base class for actions is the L<Workflow::Action> class.
613
614=item *
615
616B<condition> - Within the workflow you can attach one or more
617conditions to an action. These ensure that actions only get executed
618when certain conditions are met. Conditions are completely arbitrary:
619typically they will ensure the user has particular access rights, but
620you can also specify that an action can only be executed at certain
621times of the day, or from certain IP addresses, and so forth. Each
622condition is created once at startup then passed a context to check
623every time an action is checked to see if it can be executed.
624
625The base class for conditions is the L<Workflow::Condition> class.
626
627=item *
628
629B<validator> - An action can specify one or more validators to ensure
630that the data available to the action is correct. The data to check
631can be as simple or complicated as you like. Each validator is created
632once then passed a context and data to check every time an action is
633executed.
634
635The base class for validators is the L<Workflow::Validator> class.
636
637=back
638
639=head1 WORKFLOW BASICS
640
641=head2 Just a Bunch of States
642
643A workflow is just a bunch of states with rules on how to move between
644them. These are known as transitions and are triggered by some sort of
645event. A state is just a description of object properties. You can
646describe a surprisingly large number of processes as a series of
647states and actions to move between them. The application shipped with
648this distribution uses a fairly common application to illustrate: the
649trouble ticket.
650
651When you create a workflow you have one action available to you:
652create a new ticket ('create issue'). The workflow has a state
653'INITIAL' when it is first created, but this is just a bootstrapping
654exercise since the workflow must always be in some state.
655
656The workflow action 'create issue' has a property 'resulting_state',
657which just means: if you execute me properly the workflow will be in
658the new state 'CREATED'.
659
660All this talk of 'states' and 'transitions' can be confusing, but just
661match them to what happens in real life -- you move from one action to
662another and at each step ask: what happens next?
663
664You create a trouble ticket: what happens next? Anyone can add
665comments to it and attach files to it while administrators can edit it
666and developers can start working on it. Adding comments does not
667really change what the ticket is, it just adds
668information. Attachments are the same, as is the admin editing the
669ticket.
670
671But when someone starts work on the ticket, that is a different
672matter. When someone starts work they change the answer to: what
673happens next? Whenever the answer to that question changes, that means
674the workflow has changed state.
675
676=head2 Discover Information from the Workflow
677
678In addition to declaring what the resulting state will be from an
679action the action also has a number of 'field' properties that
680describe that data it required to properly execute it.
681
682This is an example of discoverability. This workflow system is setup
683so you can ask it what you can do next as well as what is required to
684move on. So to use our ticket example we can do this, creating the
685workflow and asking it what actions we can execute right now:
686
687 my $wf = Workflow::$self->_factory()->create_workflow( 'Ticket' );
688 my @actions = $wf->get_current_actions;
689
690We can also interrogate the workflow about what fields are necessary
691to execute a particular action:
692
693 print "To execute the action 'create issue' you must provide:\n\n";
694 my @fields = $wf->get_action_fields( 'create issue' );
695 foreach my $field ( @fields ) {
696     print $field->name, " (Required? ", $field->is_required, ")\n",
697           $field->description, "\n\n";
698 }
699
700=head2 Provide Information to the Workflow
701
702To allow the workflow to run into multiple environments we must have a
703common way to move data between your application, the workflow and the
704code that moves it from one state to another.
705
706Whenever the L<Workflow::Factory> creates a new workflow it associates
707the workflow with a L<Workflow::Context> object. The context is what
708moves the data from your application to the workflow and the workflow
709actions.
710
711For instance, the workflow has no idea what the 'current user' is. Not
712only is it unaware from an application standpoint but it does not
713presume to know where to get this information. So you need to tell it,
714and you do so through the context.
715
716The fact that the workflow system proscribes very little means it can
717be used in lots of different applications and interfaces. If a system
718is too closely tied to an interface (like the web) then you have to
719create some potentially ugly hacks to create a more convenient avenue
720for input to your system (such as an e-mail approving a document).
721
722The L<Workflow::Context> object is extremely simple to use -- you ask
723a workflow for its context and just get/set parameters on it:
724
725 # Get the username from the Apache object
726 my $username = $r->connection->user;
727
728 # ...set it in the context
729 $wf->context->param( user => $username );
730
731 # somewhere else you'll need the username:
732
733 $news_object->{created_by} = $wf->context->param( 'user' );
734
735=head2 Controlling What Gets Executed
736
737A typical process for executing an action is:
738
739=over 4
740
741=item *
742
743Get data from the user
744
745=item *
746
747Fetch a workflow
748
749=item *
750
751Set the data from the user to the workflow context
752
753=item *
754
755Execute an action on the context
756
757=back
758
759When you execute the action a number of checks occur. The action needs
760to ensure:
761
762=over 4
763
764=item *
765
766The data presented to it are valid -- date formats, etc. This is done
767with a validator, more at L<Workflow::Validator>
768
769=item *
770
771The environment meets certain conditions -- user is an administrator,
772etc. This is done with a condition, more at L<Workflow::Condition>
773
774=back
775
776Once the action passes these checks and successfully executes we
777update the permanent workflow storage with the new state, as long as
778the application has declared it.
779
780=head1 WORKFLOWS ARE OBSERVABLE
781
782=head2 Purpose
783
784It's useful to have your workflow generate events so that other parts
785of a system can see what's going on and react. For instance, say you
786have a new user creation process. You want to email the records of all
787users who have a first name of 'Sinead' because you're looking for
788your long-lost sister named 'Sinead'. You'd create an observer class
789like:
790
791 package FindSinead;
792
793 sub update {
794     my ( $class, $wf, $event, $new_state ) = @_;
795     return unless ( $event eq 'state change' );
796     return unless ( $new_state eq 'CREATED' );
797     my $context = $wf->context;
798     return unless ( $context->param( 'first_name' ) eq 'Sinead' );
799
800     my $user = $context->param( 'user' );
801     my $username = $user->username;
802     my $email    = $user->email;
803     my $mailer = get_mailer( ... );
804     $mailer->send( 'foo@bar.com','Found her!',
805                    "We found Sinead under '$username' at '$email' );
806 }
807
808And then associate it with your workflow:
809
810 <workflow>
811     <type>SomeFlow</type>
812     <observer class="FindSinead" />
813     ...
814
815Every time you create/fetch a workflow the associated observers are
816attached to it.
817
818=head2 Events Generated
819
820You can attach listeners to workflows and catch events at a few points
821in the workflow lifecycle; these are the events fired:
822
823=over 4
824
825=item *
826
827B<create> - Issued after a workflow is first created.
828
829No additional parameters.
830
831=item *
832
833B<fetch> - Issued after a workflow is fetched from the persister.
834
835No additional parameters.
836
837=item *
838
839B<save> - Issued after a workflow is successfully saved.
840
841No additional parameters.
842
843=item *
844
845B<execute> - Issued after a workflow is successfully executed and
846saved.
847
848Adds the parameters C<$old_state>, C<$action_name> and C<$autorun>.
849C<$old_state> includes the state of the workflow before the action
850was executed, C<$action_name> is the action name that was executed and
851C<$autorun> is set to 1 if the action just executed was started
852using autorun.
853
854=item *
855
856B<state change> - Issued after a workflow is successfully executed,
857saved and results in a state change. The event will not be fired if
858you executed an action that did not result in a state change.
859
860Adds the parameters C<$old_state>, C<$action> and C<$autorun>.
861C<$old_state> includes the state of the workflow before the action
862was executed, C<$action> is the action name that was executed and
863C<$autorun> is set to 1 if the action just executed was autorun.
864
865=item *
866
867B<add history> - Issued after one or more history objects added to a
868workflow object.
869
870The additional argument is an arrayref of all L<Workflow::History>
871objects added to the workflow. (Note that these will not be persisted
872until the workflow is persisted.)
873
874=back
875
876=head2 Configuring
877
878You configure the observers directly in the 'workflow' configuration
879item. Each 'observer' may have either a 'class' or 'sub' entry within
880it that defines the observer's location.
881
882We load these classes at startup time. So if you specify an observer
883that doesn't exist you see the error when the workflow system is
884initialized rather than the system tries to use the observer.
885
886For instance, the following defines two observers:
887
888 <workflow>
889   <type>ObservedItem</type>
890   <description>This is...</description>
891
892   <observer class="SomeObserver" />
893   <observer sub="SomeOtherObserver::Functions::other_sub" />
894
895In the first declaration we specify the class ('SomeObserver') that
896will catch observations using its C<update()> method. In the second
897we're naming exactly the subroutine ('other_sub()' in the class
898'SomeOtherObserver::Functions') that will catch observations.
899
900All configured observers get all events. It's up to each observer to
901figure out what it wants to handle.
902
903=head1 WORKFLOW METHODS
904
905The following documentation is for the workflow object itself rather
906than the entire system.
907
908=head2 Object Methods
909
910=head3 execute_action( $action_name, $autorun )
911
912Execute the action C<$action_name>. Typically this changes the state
913of the workflow. If C<$action_name> is not in the current state, fails
914one of the conditions on the action, or fails one of the validators on
915the action an exception is thrown. $autorun is used internally and
916is set to 1 if the action was executed using autorun.
917
918After the action has been successfully executed and the workflow saved
919we issue a 'execute' observation with the old state, action name and
920an autorun flag as additional parameters.
921So if you wanted to write an observer you could create a
922method with the signature:
923
924 sub update {
925     my ( $class, $workflow, $action, $old_state, $action_name, $autorun )
926        = @_;
927     if ( $action eq 'execute' ) { .... }
928 }
929
930We also issue a 'change state' observation if the executed action
931resulted in a new state. See L<WORKFLOWS ARE OBSERVABLE> above for how
932we use and register observers.
933
934Returns: new state of workflow
935
936=head3 get_current_actions( $group )
937
938Returns a list of action names available from the current state for
939the given environment. So if you keep your C<context()> the same if
940you call C<execute_action()> with one of the action names you should
941not trigger any condition error since the action has already been
942screened for conditions.
943If you want to divide actions in groups (for example state change group,
944approval group, which have to be shown at different places on the page) add group property
945to your action
946
947<action name="terminate request"  group="state change"  class="MyApp::Action::Terminate" />
948<action name="approve request"  group="approval"  class="MyApp::Action::Approve" />
949
950my @actions = $wf->get_current_actions("approval");
951
952$group should be string that reperesents desired group name. In @actions you will get
953list of action names available from the current state for the given environment limited by group.
954$group is optional parameter.
955
956Returns: list of strings representing available actions
957
958=head3 get_action( $action_name )
959
960Retrieves the action object associated with C<$action_name> in the
961current workflow state. This will throw an exception if:
962
963=over 4
964
965=item *
966
967No workflow state exists with a name of the current state. (This is
968usually some sort of configuration error and should be caught at
969initialization time, so it should not happen.)
970
971=item *
972
973No action C<$action_name> exists in the current state.
974
975=item *
976
977No action C<$action_name> exists in the workflow universe.
978
979=item *
980
981One of the conditions for the action in this state is not met.
982
983=back
984
985
986=head3 get_action_fields( $action_name )
987
988Return a list of L<Workflow::Action::InputField> objects for the given
989C<$action_name>. If C<$action_name> not in the current state or not
990accessible by the environment an exception is thrown.
991
992Returns: list of L<Workflow::Action::InputField> objects
993
994=head3 add_history( @( \%params | $wf_history_object ) )
995
996Adds any number of histories to the workflow, typically done by an
997action in C<execute_action()> or one of the observers of that
998action. This history will not be saved until C<execute_action()> is
999complete.
1000
1001You can add a list of either hashrefs with history information in them
1002or full L<Workflow::History> objects. Trying to add anything else will
1003result in an exception and B<none> of the items being added.
1004
1005Successfully adding the history objects results in a 'add history'
1006observation being thrown. See L<WORKFLOWS ARE OBSERVABLE> above for
1007more.
1008
1009Returns: nothing
1010
1011=head3 get_history()
1012
1013Returns list of history objects for this workflow. Note that some may
1014be unsaved if you call this during the C<execute_action()> process.
1015
1016=head3 get_unsaved_history()
1017
1018Returns list of all unsaved history objects for this workflow.
1019
1020=head3 clear_history()
1021
1022Clears all transient history objects from the workflow object, B<not>
1023from the long-term storage.
1024
1025=head3 set( $property, $value )
1026
1027Method used to overwrite L<Class::Accessor> so only certain callers can set
1028properties caller has to be a L<Workflow> namespace package.
1029
1030Sets property to value or throws L<Workflow::Exception>
1031
1032=head2 Properties
1033
1034Unless otherwise noted, properties are B<read-only>.
1035
1036=head3 Configuration Properties
1037
1038Some properties are set in the configuration file for each
1039workflow. These remain static once the workflow is instantiated.
1040
1041B<type>
1042
1043Type of workflow this is. You may have many individual workflows
1044associated with a type or you may have many different types
1045running in a single workflow engine.
1046
1047B<description>
1048
1049Description (usually brief, hopefully with a URL...)  of this
1050workflow.
1051
1052B<time_zone>
1053
1054Workflow uses the DateTime module to create all date objects. The time_zone
1055parameter allows you to pass a time zone value directly to the DateTime
1056new method for all cases where Workflow needs to create a date object.
1057See the DateTime module for acceptable values.
1058
1059=head3 Dynamic Properties
1060
1061You can get the following properties from any workflow object.
1062
1063B<id>
1064
1065ID of this workflow. This will B<always> be defined, since when the
1066L<Workflow::Factory> creates a new workflow it first saves it to
1067long-term storage.
1068
1069B<state>
1070
1071The current state of the workflow.
1072
1073B<last_update> (read-write)
1074
1075Date of the workflow's last update.
1076
1077=head3 context (read-write, see below)
1078
1079A L<Workflow::Context> object associated with this workflow. This
1080should never be undefined as the L<Workflow::Factory> sets an empty
1081context into the workflow when it is instantiated.
1082
1083If you add a context to a workflow and one already exists, the values
1084from the new workflow will overwrite values in the existing
1085workflow. This is a shallow merge, so with the following:
1086
1087 $wf->context->param( drinks => [ 'coke', 'pepsi' ] );
1088 my $context = Workflow::Context->new();
1089 $context->param( drinks => [ 'beer', 'wine' ] );
1090 $wf->context( $context );
1091 print 'Current drinks: ', join( ', ', @{ $wf->context->param( 'drinks' ) } );
1092
1093You will see:
1094
1095 Current drinks: beer, wine
1096
1097=head2 Internal Methods
1098
1099=head3 init( $id, $current_state, \%workflow_config, \@wf_states )
1100
1101B<THIS SHOULD ONLY BE CALLED BY THE> L<Workflow::Factory>. Do not call
1102this or the C<new()> method yourself -- you will only get an
1103exception. Your only interface for creating and fetching workflows is
1104through the factory.
1105
1106This is called by the inherited constructor and sets the
1107C<$current_state> value to the property C<state> and uses the other
1108non-state values from C<\%config> to set parameters via the inherited
1109C<param()>.
1110
1111=head3 _get_workflow_state( [ $state ] )
1112
1113Return the L<Workflow::State> object corresponding to C<$state>, which
1114defaults to the current state.
1115
1116=head3 _set_workflow_state( $wf_state )
1117
1118Assign the L<Workflow::State> object C<$wf_state> to the workflow.
1119
1120=head3 _get_next_state( $action_name )
1121
1122Returns the name of the next state given the action
1123C<$action_name>. Throws an exception if C<$action_name> not contained
1124in the current state.
1125
1126=head3 add_observer( @observers )
1127
1128Adds one or more observers to a C<Workflow> instance. An observer is a
1129function. See L</notify_observers> for its calling convention.
1130
1131This function is used internally by C<Workflow::Factory> to implement
1132observability as documented in the section L</WORKFLOWS ARE OBSERVABLE>
1133
1134=head3 notify_observers( @arguments )
1135
1136Calls all observer functions registered through C<add_observer> with
1137the workflow as the first argument and C<@arguments> as the remaining
1138arguments:
1139
1140   $observer->( $wf, @arguments );
1141
1142Used by various parts of the library to notify observers of workflow
1143instance related events.
1144
1145
1146=head1 CONFIGURATION AND ENVIRONMENT
1147
1148The configuration of Workflow is done using the format of your choice, currently
1149XML and Perl is implemented, but additional formats can be added, please refer
1150to L<Workflow::Config>, for implementation details.
1151
1152=head1 DEPENDENCIES
1153
1154=over
1155
1156=item L<Class::Accessor>
1157
1158=item L<Class::Factory>
1159
1160=item L<DateTime>
1161
1162=item L<DateTime::Format::Strptime>
1163
1164=item L<Exception::Class>
1165
1166=item L<Log::Log4perl>
1167
1168=item L<Safe>
1169
1170=item L<XML::Simple>
1171
1172=item L<DBI>
1173
1174=item L<Data::Dumper>
1175
1176=item L<Carp>
1177
1178=item L<File::Slurp>
1179
1180=item L<Data::UUID>
1181
1182=back
1183
1184=head2 DEPENDENCIES FOR THE EXAMPLE APPLICATION
1185
1186=over
1187
1188=item L<CGI>
1189
1190=item L<CGI::Cookie>
1191
1192=item L<DBD::SQLite>
1193
1194=item L<HTTP::Daemon>
1195
1196=item L<HTTP::Request>
1197
1198=item L<HTTP::Response>
1199
1200=item L<HTTP::Status>
1201
1202=item L<Template> (Template Toolkit)
1203
1204=back
1205
1206For Win32 systems you can get the Template Toolkit and DBD::SQLite
1207PPDs from TheoryX:
1208
1209=over
1210
1211=item * L<http://theoryx5.uwinnipeg.ca/cgi-bin/ppmserver?urn:/PPMServer58>
1212
1213=back
1214
1215=head1 INCOMPATIBILITIES
1216
1217=head2 XML::Simple
1218
1219CPAN testers reports however do demonstrate a problem with one of the
1220dependencies of Workflow, namely L<XML::Simple>.
1221
1222The L<XML::Simple> makes use of L<Lib::XML::SAX> or L<XML::Parser>, the default.
1223
1224In addition an L<XML::Parser> can makes use of plugin parser and some of these
1225might not be able to parse the XML utilized in Workflow. The problem have been
1226observed with L<XML::SAX::RTF>.
1227
1228The following diagnostic points to the problem:
1229
1230        No _parse_* routine defined on this driver (If it is a filter, remember to
1231        set the Parent property. If you call the parse() method, make sure to set a
1232        Source. You may want to call parse_uri, parse_string or parse_file instead.)
1233
1234Your L<XML::SAX> configuration is located in the file:
1235
1236        XML/SAX/ParserDetails.ini
1237
1238=head1 BUGS AND LIMITATIONS
1239
1240Known bugs and limitations can be seen in the Github issue tracker:
1241
1242L<https://github.com/jonasbn/perl-workflow/issues>
1243
1244=head1 BUG REPORTING
1245
1246Bug reporting should be done either via Github issues
1247
1248L<https://github.com/jonasbn/perl-workflow/issues>
1249
1250A list of currently known issues can be seen via the same URL.
1251
1252=head1 TEST
1253
1254The test suite can be run using, L<Module::Build>
1255
1256        % ./Build test
1257
1258Some of the tests are reserved for the developers and are only run of the
1259environment variable C<TEST_AUTHOR> is set to true.
1260
1261=head1 TEST COVERAGE
1262
1263This is the current test coverage of Workflow version 1.58, with the C<TEST_AUTHOR>
1264flag enabled
1265
1266    TEST_AUTHOR=1 dzil cover
1267
1268    ---------------------------- ------ ------ ------ ------ ------ ------ ------
1269    File                           stmt   bran   cond    sub    pod   time  total
1270    ---------------------------- ------ ------ ------ ------ ------ ------ ------
1271    blib/lib/Workflow.pm           91.6   68.7   60.0   93.3  100.0    1.2   86.7
1272    blib/lib/Workflow/Action.pm    93.5   60.0    n/a   94.1  100.0    4.4   91.4
1273    ...b/Workflow/Action/Null.pm  100.0    n/a    n/a  100.0  100.0    2.3  100.0
1274    blib/lib/Workflow/Base.pm      96.7   86.3   83.3  100.0  100.0    3.0   94.5
1275    ...lib/Workflow/Condition.pm  100.0  100.0  100.0  100.0  100.0    4.4  100.0
1276    .../Condition/CheckReturn.pm   71.7   35.7    n/a  100.0  100.0    0.0   67.6
1277    ...low/Condition/Evaluate.pm   96.7   75.0    n/a  100.0  100.0    3.4   95.4
1278    ...low/Condition/GreedyOR.pm  100.0  100.0    n/a  100.0  100.0    0.0  100.0
1279    ...flow/Condition/HasUser.pm   70.0    n/a   33.3   83.3  100.0    0.0   70.0
1280    ...flow/Condition/LazyAND.pm  100.0  100.0    n/a  100.0  100.0    0.0  100.0
1281    ...kflow/Condition/LazyOR.pm  100.0  100.0    n/a  100.0  100.0    0.0  100.0
1282    ...flow/Condition/Negated.pm  100.0    n/a    n/a  100.0  100.0    0.0  100.0
1283    blib/lib/Workflow/Config.pm    96.2   81.2   33.3  100.0  100.0    3.1   92.2
1284    ...b/Workflow/Config/Perl.pm   96.1   83.3   66.6   92.8  100.0    0.1   92.9
1285    ...ib/Workflow/Config/XML.pm   94.1   62.5   50.0  100.0  100.0    4.6   90.2
1286    blib/lib/Workflow/Context.pm  100.0    n/a    n/a  100.0  100.0    2.3  100.0
1287    ...lib/Workflow/Exception.pm  100.0  100.0    n/a  100.0  100.0    0.9  100.0
1288    blib/lib/Workflow/Factory.pm   87.4   79.3   61.5   84.6  100.0   30.9   84.3
1289    blib/lib/Workflow/History.pm  100.0   87.5    n/a  100.0  100.0    4.3   98.2
1290    ...ib/Workflow/InputField.pm   98.6   96.1   87.5  100.0  100.0    2.5   97.6
1291    ...lib/Workflow/Persister.pm   98.4  100.0   71.4   94.7  100.0    2.4   96.4
1292    ...Workflow/Persister/DBI.pm   86.7   72.0   35.2   90.6  100.0    7.7   83.0
1293    ...er/DBI/AutoGeneratedId.pm   91.8   75.0   83.3  100.0  100.0    0.0   88.7
1294    ...ersister/DBI/ExtraData.pm   29.8    0.0    0.0   60.0  100.0    0.6   29.7
1295    ...rsister/DBI/SequenceId.pm  100.0    n/a   50.0  100.0  100.0    0.0   98.0
1296    ...orkflow/Persister/File.pm   94.4   50.0   33.3  100.0  100.0    0.2   88.5
1297    ...low/Persister/RandomId.pm  100.0    n/a  100.0  100.0  100.0    2.3  100.0
1298    ...orkflow/Persister/UUID.pm  100.0    n/a    n/a  100.0  100.0    2.2  100.0
1299    blib/lib/Workflow/State.pm     88.1   62.5   16.6   96.3  100.0    4.9   81.7
1300    ...lib/Workflow/Validator.pm  100.0   83.3    n/a  100.0  100.0    2.4   97.5
1301    ...dator/HasRequiredField.pm   90.9   50.0    n/a  100.0  100.0    2.3   87.8
1302    ...dator/InEnumeratedType.pm  100.0  100.0    n/a  100.0  100.0    2.3  100.0
1303    ...ator/MatchesDateFormat.pm  100.0  100.0  100.0  100.0  100.0    4.0  100.0
1304    Total                          90.7   73.6   57.6   94.9  100.0  100.0   87.8
1305    ---------------------------- ------ ------ ------ ------ ------ ------ ------
1306
1307Activities to get improved coverage are ongoing.
1308
1309=head1 QUALITY ASSURANCE
1310
1311The Workflow project utilizes L<Perl::Critic> in an attempt to avoid common
1312pitfalls and programming mistakes.
1313
1314The static analysis performed by L<Perl::Critic> is integrated into the L</TEST>
1315tool chain and is performed either by running the test suite.
1316
1317        % ./Build test
1318
1319Or by running the test file containing the L<Perl::Critic> tests explicitly.
1320
1321        % ./Build test --verbose 1 --test_files t/04_critic.t
1322
1323Or
1324
1325        % perl t/critic.t
1326
1327The test does however require that the TEST_AUTHOR flag is set since this is
1328regarded as a part of the developer tool chain and we do not want to disturb
1329users and CPAN testers with this.
1330
1331The following policies are disabled
1332
1333=over
1334
1335=item * L<Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers>
1336
1337=item * L<Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef>
1338
1339=item * L<Perl::Critic::Policy::NamingConventions::ProhibitAmbiguousNames>
1340
1341=item * L<Perl::Critic::Policy::ValuesAndExpressions::ProhibitConstantPragma>
1342
1343=back
1344
1345The complete policy configuration can be found in t/perlcriticrc.
1346
1347Currently a large number other policies are disabled, but these are being
1348addressed as ongoing work and they will either be listed here or changes will
1349be applied, which will address the Workflow code's problematic areas from
1350L<Perl::Critic> perspective.
1351
1352=head1 CODING STYLE
1353
1354Currently the code is formatted using L<Perl::Tidy>. The resource file can be
1355downloaded from the central repository.
1356
1357        notes/perltidyrc
1358
1359=head1 PROJECT
1360
1361The Workflow project is currently hosted on GitHub
1362
1363=over
1364
1365=item GitHub: L<https://github.com/jonasbn/perl-workflow>
1366
1367=back
1368
1369=head2 REPOSITORY
1370
1371The code is kept under revision control using Git:
1372
1373=over
1374
1375=item L<https://github.com/jonasbn/perl-workflow/tree/master/>
1376
1377=back
1378
1379=head2 OTHER RESOURCES
1380
1381=over
1382
1383=item * CPAN Ratings
1384
1385L<http://cpanratings.perl.org/d/Workflow>
1386
1387=item * MetaCPAN
1388
1389L<https://metacpan.org/release/Workflow>
1390
1391=back
1392
1393=head1 SEE ALSO
1394
1395=over
1396
1397=item * November 2010 talk 'Workflow' given at Nordic Perl Workshop 2010 in Reykjavik, Iceland by jonasbn
1398L<http://www.slideshare.net/jonasbn/workflow-npw2010>
1399
1400=item * August 2010 talk 'Workflow' given at YAPC::Europe 2010 in Pisa, Italy by jonasbn
1401L<http://www.slideshare.net/jonasbn/workflow-yapceu2010>
1402
1403=back
1404
1405=head1 COPYRIGHT
1406
1407Copyright (c) 2003 Chris Winters and Arvato Direct;
1408Copyright (c) 2004-2022 Chris Winters. All rights reserved.
1409
1410This library is free software; you can redistribute it and/or modify
1411it under the same terms as Perl itself.
1412
1413=head1 AUTHORS
1414
1415=encoding utf8
1416
1417Jonas B. (jonasbn) E<lt>jonasbn@cpan.orgE<gt>, current maintainer.
1418
1419Chris Winters E<lt>chris@cwinters.comE<gt>, original author.
1420
1421The following folks have also helped out (listed here in no particular order):
1422
1423Thanks for to Michiel W. Beijen for fix to badly formatted URL, included in release 1.52
1424
1425Several PRs (13 to be exact) from Erik Huelsmann resulting in release 1.49. Yet another
1426batch of PRs resulted in release 1.50
1427
1428PR from Mohammad S Anwar correcting some POD errors, included in release 1.49
1429
1430Bug report from Petr Pisar resulted in release 1.48
1431
1432Bug report from Tina Müller (tinita) resulted in release 1.47
1433
1434Bug report from Slaven Rezić resulting in maintenance release 1.45
1435
1436Feature and bug fix by dtikhonov resulting in 1.40 (first pull request on Github)
1437
1438Sérgio Alves, patch to timezone handling for workflow history deserialized using
1439DBI persister resulting in 1.38
1440
1441Heiko Schlittermann for context serialization patch resulting in 1.36
1442
1443Scott Harding, for lazy evaluation of conditions and for nested conditions, see
1444Changes file: 1.35
1445
1446Oliver Welter, patch implementing custom workflows, see Changes file: 1.35 and
1447patch related to this in 1.37 and factory subclassing also in 1.35. Improvements
1448in logging for condition validation in 1.43 and 1.44 and again a patch resulting
1449in release 1.46
1450
1451Steven van der Vegt, patch for autorun in initial state and improved exception
1452handling for validators, see Changes file: 1.34_1
1453
1454Andrew O'Brien, patch implementing dynamic reloaded of flows, see Changes file:
14551.33
1456
1457Sergei Vyshenski, bug reports - addressed and included in 1.33, Sergei also
1458maintains the FreeBSD port
1459
1460Alejandro Imass, improvements and clarifications, see Changes file: 1.33
1461
1462Danny Sadinoff, patches to give better control of initial state and history
1463records for workflow, see Changes file: 1.33
1464
1465Thomas Erskine, for patch adding new accessors and fixing several bugs see
1466Changes file 1.33
1467
1468Ivan Paponov, for patch implementing action groups, see Changes file, 1.33
1469
1470Robert Stockdale, for patch implementing dynamic names for conditions, see
1471Changes file, 1.32
1472
1473Jim Brandt, for patch to Workflow::Config::XML. See Changes file, 0.27 and 0.30
1474
1475Alexander Klink, for: patches resulting in 0.23, 0.24, 0.25, 0.26 and 0.27
1476
1477Michael Bell, for patch resulting in 0.22
1478
1479Martin Bartosch, for bug reporting and giving the solution not even using a
1480patch (0.19 to 0.20) and a patch resulting in 0.21
1481
1482Randal Schwartz, for testing 0.18 and swiftly giving feedback (0.18 to 0.19)
1483
1484Chris Brown, for a patch to L<Workflow::Config::Perl> (0.17 to 0.18)
1485
1486Dietmar Hanisch E<lt>Dietmar.Hanisch@Bertelsmann.deE<gt> - Provided
1487most of the good ideas for the module and an excellent example of
1488everyday use.
1489
1490Tom Moertel E<lt>tmoertel@cpan.orgE<gt> gave me the idea for being
1491able to attach event listeners (observers) to the process.
1492
1493Michael Roberts E<lt>michael@vivtek.comE<gt> graciously released the
1494'Workflow' namespace on CPAN; check out his Workflow toolkit at
1495L<http://www.vivtek.com/wftk.html>.
1496
1497Michael Schwern E<lt>schwern@pobox.orgE<gt> barked via RT about a
1498dependency problem and CPAN naming issue.
1499
1500Jim Smith E<lt>jgsmith@tamu.eduE<gt> - Contributed patches (being able
1501to subclass L<Workflow::Factory>) and good ideas.
1502
1503Martin Winkler E<lt>mw@arsnavigandi.deE<gt> - Pointed out a bug and a
1504few other items.
1505
1506=cut
1507