File Coverage

lib/Workflow.pm
Criterion Covered Total %
statement 167 192 86.9
branch 29 46 63.0
condition 10 15 66.6
subroutine 28 30 93.3
pod 13 13 100.0
total 247 296 83.4


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