1Workflow(3) User Contributed Perl Documentation Workflow(3)
2
3
4
6 Workflow - Simple, flexible system to implement workflows
7
9 use Workflow::Factory qw( FACTORY );
10
11 # Defines a workflow of type 'myworkflow'
12 my $workflow_conf = 'workflow.xml';
13
14 # contents of 'workflow.xml'
15
16 <workflow>
17 <type>myworkflow</type>
18 <state name="INITIAL">
19 <action name="upload file" resulting_state="uploaded" />
20 </state>
21 <state name="uploaded" autorun="yes">
22 <action name="verify file" resulting_state="verified file">
23 <!-- everyone other than 'CWINTERS' must verify -->
24 <condition test="$context->{user} ne 'CWINTERS'" />
25 </action>
26 <action name="null" resulting_state="annotated">
27 <condition test="$context->{user} eq 'CWINTERS'" />
28 </action>
29 </state>
30 <state name="verified file">
31 <action name="annotate">
32 <condition name="can_annotate" />
33 </action>
34 <action name="null">
35 <condition name="!can_annotate" />
36 </action>
37 </state>
38 <state name="annotated" autorun="yes" may_stop="yes">
39 <action name="null" resulting_state="finished">
40 <condition name="completed" />
41 </action>
42 </state>
43 <state name="finished" />
44 </workflow>
45
46 # Defines actions available to the workflow
47 my $action_conf = 'action.xml';
48
49 # contents of 'action.xml'
50
51 <actions>
52 <action name="upload file" class="MyApp::Action::Upload">
53 <field name="path" label="File Path"
54 description="Path to file" is_required="yes" />
55 </action>
56
57 <action name="verify file" class="MyApp::Action::Verify">
58 <validator name="filesize_cap">
59 <arg>$file_size</arg>
60 </validator>
61 </action>
62
63 <action name="annotate" class="MyApp::Action::Annotate" />
64
65 <action name="null" class="Workflow::Action::Null" />
66 </actions>
67
68 # Defines conditions available to the workflow
69 my $condition_conf = 'condition.xml';
70
71 # contents of 'condition.xml'
72
73 <conditions>
74 <condition name="can_annotate"
75 class="MyApp::Condition::CanAnnotate" />
76 </conditions>
77
78 # Defines validators available to the actions
79 my $validator_conf = 'validator.xml';
80
81 # contents of 'validator.xml'
82
83 <validators>
84 <validator name="filesize_cap" class="MyApp::Validator::FileSizeCap">
85 <param name="max_size" value="20M" />
86 </validator>
87 </validators>
88
89 # Stock the factory with the configurations; we can add more later if
90 # we want
91 FACTORY->add_config_from_file(
92 workflow => $workflow_conf,
93 action => $action_conf,
94 condition => $condition_conf,
95 validator => $validator_conf
96 );
97
98 # Instantiate a new workflow...
99 my $workflow = FACTORY->create_workflow( 'myworkflow' );
100 print "Workflow ", $workflow->id, " ",
101 "currently at state ", $workflow->state, "\n";
102
103 # Display available actions...
104 print "Available actions: ", $workflow->get_current_actions, "\n";
105
106 # Get the data needed for action 'upload file' (assumed to be
107 # available in the current state) and display the fieldname and
108 # description
109
110 print "Action 'upload file' requires the following fields:\n";
111 foreach my $field ( $workflow->get_action_fields( 'FOO' ) ) {
112 print $field->name, ": ", $field->description,
113 "(Required? ", $field->is_required, ")\n";
114 }
115
116 # Add data to the workflow context for the validators, conditions and
117 # actions to work with
118
119 my $context = $workflow->context;
120 $context->param( current_user => $user );
121 $context->param( sections => \@sections );
122 $context->param( path => $path_to_file );
123
124 # Execute one of them
125 $workflow->execute_action( 'upload file' );
126
127 print "New state: ", $workflow->state, "\n";
128
129 # Later.... fetch an existing workflow
130 my $id = get_workflow_id_from_user( ... );
131 my $workflow = FACTORY->fetch_workflow( 'myworkflow', $id );
132 print "Current state: ", $workflow->state, "\n";
133
135 This documentation is for version '0.15' of the Workflow module.
136
137 Overview
138
139 This is a standalone workflow system. It is designed to fit into your
140 system rather than force your system to fit to it. You can save work‐
141 flow information to a database or the filesystem (or a custom storage).
142 The different components of a workflow system can be included sepa‐
143 rately as libraries to allow for maximum reusibility.
144
145 User Point of View
146
147 As a user you only see two components, plus a third which is really
148 embedded into another:
149
150 · Workflow::Factory - The factory is your interface for creating new
151 workflows and fetching existing ones. You also feed all the neces‐
152 sary configuration files and/or data structures to the factory to
153 initialize it.
154
155 · Workflow - When you get the workflow object from the workflow fac‐
156 tory you can only use it in a few ways -- asking for the current
157 state, actions available for the state, data required for a partic‐
158 ular action, and most importantly, executing a particular action.
159 Executing an action is how you change from one state to another.
160
161 · Workflow::Context - This is a blackboard for data from your appli‐
162 cation to the workflow system and back again. Each instantiation of
163 a Workflow has its own context, and actions executed by the work‐
164 flow can read data from and deposit data into the context.
165
166 Developer Point of View
167
168 The workflow system has four basic components:
169
170 · workflow - The workflow is a collection of states; you define the
171 states, how to move from one state to another, and under what con‐
172 ditions you can change states.
173
174 This is represented by the Workflow object. You normally do not
175 need to subclass this object for customization.
176
177 · action - The action is defined by you or in a separate library. The
178 action is triggered by moving from one state to another and has
179 access to the workflow and more importantly its context.
180
181 The base class for actions is the Workflow::Action class.
182
183 · condition - Within the workflow you can attach one or more condi‐
184 tions to an action. These ensure that actions only get executed
185 when certain conditions are met. Conditions are completely arbi‐
186 trary: typically they will ensure the user has particular access
187 rights, but you can also specify that an action can only be exe‐
188 cuted at certain times of the day, or from certain IP addresses,
189 and so forth. Each condition is created once at startup then passed
190 a context to check every time an action is checked to see if it can
191 be executed.
192
193 The base class for conditions is the Workflow::Condition class.
194
195 · validator - An action can specify one or more validators to ensure
196 that the data available to the action is correct. The data to check
197 can be as simple or complicated as you like. Each validator is cre‐
198 ated once then passed a context and data to check every time an
199 action is executed.
200
201 The base class for validators is the Workflow::Validator class.
202
204 Just a Bunch of States
205
206 A workflow is just a bunch of states with rules on how to move between
207 them. These are known as transitions and are triggered by some sort of
208 event. A state is just a description of object properties. You can
209 describe a surprisingly large number of processes as a series of states
210 and actions to move between them. The application shipped with this
211 distribution uses a fairly common application to illustrate: the trou‐
212 ble ticket.
213
214 When you create a workflow you have one action available to you: create
215 a new ticket ('create issue'). The workflow has a state 'INITIAL' when
216 it is first created, but this is just a bootstrapping exercise since
217 the workflow must always be in some state.
218
219 The workflow action 'create issue' has a property 'resulting_state',
220 which just means: if you execute me properly the workflow will be in
221 the new state 'CREATED'.
222
223 All this talk of 'states' and 'transitions' can be confusing, but just
224 match them to what happens in real life -- you move from one action to
225 another and at each step ask: what happens next?
226
227 You create a trouble ticket: what happens next? Anyone can add comments
228 to it and attach files to it while administrators can edit it and
229 developers can start working on it. Adding comments does not really
230 change what the ticket is, it just adds information. Attachments are
231 the same, as is the admin editing the ticket.
232
233 But when someone starts work on the ticket, that is a different matter.
234 When someone starts work they change the answer to: what happens next?
235 Whenever the answer to that question changes, that means the workflow
236 has changed state.
237
238 Discover Information from the Workflow
239
240 In addition to declaring what the resulting state will be from an
241 action the action also has a number of 'field' properties that describe
242 that data it required to properly execute it.
243
244 This is an example of discoverability. This workflow system is setup so
245 you can ask it what you can do next as well as what is required to move
246 on. So to use our ticket example we can do this, creating the workflow
247 and asking it what actions we can execute right now:
248
249 my $wf = Workflow::Factory->create_workflow( 'Ticket' );
250 my @actions = $wf->get_current_actions;
251
252 We can also interrogate the workflow about what fields are necessary to
253 execute a particular action:
254
255 print "To execute the action 'create issue' you must provide:\n\n";
256 my @fields = $wf->get_action_fields( 'create issue' );
257 foreach my $field ( @fields ) {
258 print $field->name, " (Required? ", $field->is_required, ")\n",
259 $field->description, "\n\n";
260 }
261
262 Provide Information to the Workflow
263
264 To allow the workflow to run into multiple environments we must have a
265 common way to move data between your application, the workflow and the
266 code that moves it from one state to another.
267
268 Whenever the Workflow::Factory creates a new workflow it associates the
269 workflow with a Workflow::Context object. The context is what moves the
270 data from your application to the workflow and the workflow actions.
271
272 For instance, the workflow has no idea what the 'current user' is. Not
273 only is it unaware from an application standpoint but it does not pre‐
274 sume to know where to get this information. So you need to tell it, and
275 you do so through the context.
276
277 The fact that the workflow system proscribes very little means it can
278 be used in lots of different applications and interfaces. If a system
279 is too closely tied to an interface (like the web) then you have to
280 create some potentially ugly hacks to create a more convenient avenue
281 for input to your system (such as an e-mail approving a document).
282
283 The Workflow::Context object is extremely simple to use -- you ask a
284 workflow for its context and just get/set parameters on it:
285
286 # Get the username from the Apache object
287 my $username = $r->connection->user;
288
289 # ...set it in the context
290 $wf->context->param( user => $username );
291
292 # somewhere else you'll need the username:
293
294 $news_object->{created_by} = $wf->context->param( 'user' );
295
296 Controlling What Gets Executed
297
298 A typical process for executing an action is:
299
300 · Get data from the user
301
302 · Fetch a workflow
303
304 · Set the data from the user to the workflow context
305
306 · Execute an action on the context
307
308 When you execute the action a number of checks occur. The action needs
309 to ensure:
310
311 · The data presented to it are valid -- date formats, etc. This is
312 done with a validator, more at Workflow::Validator
313
314 · The environment meets certain conditions -- user is an administra‐
315 tor, etc. This is done with a condition, more at Workflow::Condi‐
316 tion
317
318 Once the action passes these checks and successfully executes we update
319 the permanent workflow storage with the new state, as long as the
320 application has declared it.
321
323 Purpose
324
325 It's useful to have your workflow generate events so that other parts
326 of a system can see what's going on and react. For instance, say you
327 have a new user creation process. You want to email the records of all
328 users who have a first name of 'Sinead' because you're looking for your
329 long-lost sister named 'Sinead'. You'd create an observer class like:
330
331 package FindSinead;
332
333 sub update {
334 my ( $class, $wf, $event, $new_state ) = @_;
335 return unless ( $event eq 'state change' );
336 return unless ( $new_state eq 'CREATED' );
337 my $context = $wf->context;
338 return unless ( $context->param( 'first_name' ) eq 'Sinead' );
339
340 my $user = $context->param( 'user' );
341 my $username = $user->username;
342 my $email = $user->email;
343 my $mailer = get_mailer( ... );
344 $mailer->send( 'foo@bar.com','Found her!',
345 "We found Sinead under '$username' at '$email' );
346 }
347
348 And then associate it with your workflow:
349
350 <workflow>
351 <type>SomeFlow</type>
352 <observer class="FindSinead" />
353 ...
354
355 Every time you create/fetch a workflow the associated observers are
356 attached to it.
357
358 Events Generated
359
360 You can attach listeners to workflows and catch events at a few points
361 in the workflow lifecycle; these are the events fired:
362
363 · create - Issued after a workflow is first created.
364
365 No additional parameters.
366
367 · fetch - Issued after a workflow is fetched from the persister.
368
369 No additional parameters.
370
371 · save - Issued after a workflow is successfully saved.
372
373 No additional parameters.
374
375 · execute - Issued after a workflow is successfully executed and
376 saved.
377
378 Adds the parameters $old_state, $action_name and $autorun.
379 $old_state includes the state of the workflow before the action was
380 executed, $action_name is the action name that was executed and
381 $autorun is set to 1 if the action just executed was started using
382 autorun.
383
384 · state change - Issued after a workflow is successfully executed,
385 saved and results in a state change. The event will not be fired if
386 you executed an action that did not result in a state change.
387
388 Adds the parameters $old_state, $action and $autorun. $old_state
389 includes the state of the workflow before the action was executed,
390 $action is the action name that was executed and $autorun is set to
391 1 if the action just executed was autorun.
392
393 · add history - Issued after one or more history objects added to a
394 workflow object.
395
396 The additional argument is an arrayref of all Workflow::History
397 objects added to the workflow. (Note that these will not be per‐
398 sisted until the workflow is persisted.)
399
400 Configuring
401
402 You configure the observers directly in the 'workflow' configuration
403 item. Each 'observer' may have either a 'class' or 'sub' entry within
404 it that defines the observer's location.
405
406 We load these classes at startup time. So if you specify an observer
407 that doesn't exist you see the error when the workflow system is ini‐
408 tialized rather than the system tries to use the observer.
409
410 For instance, the following defines two observers:
411
412 <workflow>
413 <type>ObservedItem</type>
414 <description>This is...</description>
415
416 <observer class="SomeObserver" />
417 <observer sub="SomeOtherObserver::Functions::other_sub" />
418
419 In the first declaration we specify the class ('SomeObserver') that
420 will catch observations using its "update()" method. In the second
421 we're naming exactly the subroutine ('other_sub()' in the class
422 'SomeOtherObserver::Functions') that will catch observations.
423
424 All configured observers get all events. It's up to each observer to
425 figure out what it wants to handle.
426
428 The following documentation is for the workflow object itself rather
429 than the entire system.
430
431 Object Methods
432
433 execute_action( $action_name, $autorun )
434
435 Execute the action $action_name. Typically this changes the state of
436 the workflow. If $action_name is not in the current state, fails one of
437 the conditions on the action, or fails one of the validators on the
438 action an exception is thrown. $autorun is used internally and is set
439 to 1 if the action was executed using autorun.
440
441 After the action has been successfully executed and the workflow saved
442 we issue a 'execute' observation with the old state, action name and an
443 autorun flag as additional parameters. So if you wanted to write an
444 observer you could create a method with the signature:
445
446 sub update {
447 my ( $class, $workflow, $action, $old_state, $action_name, $autorun )
448 = @_;
449 if ( $action eq 'execute' ) { .... }
450 }
451
452 We also issue a 'change state' observation if the executed action
453 resulted in a new state. See "WORKFLOWS ARE OBSERVABLE" above for how
454 we use and register observers and Class::Observable for more general
455 information about observers as well as implementation details.
456
457 Returns: new state of workflow
458
459 get_current_actions()
460
461 Returns a list of action names available from the current state for the
462 given environment. So if you keep your "context()" the same if you call
463 "execute_action()" with one of the action names you should not trigger
464 any condition error since the action has already been screened for con‐
465 ditions.
466
467 Returns: list of strings representing available actions
468
469 get_action_fields( $action_name )
470
471 Return a list of Workflow::Action::InputField objects for the given
472 $action_name. If $action_name not in the current state or not accessi‐
473 ble by the environment an exception is thrown.
474
475 Returns: list of Workflow::Action::InputField objects
476
477 add_history( @( \%params ⎪ $wf_history_object ) )
478
479 Adds any number of histories to the workflow, typically done by an
480 action in "execute_action()" or one of the observers of that action.
481 This history will not be saved until "execute_action()" is complete.
482
483 You can add a list of either hashrefs with history information in them
484 or full Workflow::History objects. Trying to add anything else will
485 result in an exception and none of the items being added.
486
487 Successfully adding the history objects results in a 'add history'
488 observation being thrown. See "WORKFLOWS ARE OBSERVABLE" above for
489 more.
490
491 Returns: nothing
492
493 get_history()
494
495 Returns list of history objects for this workflow. Note that some may
496 be unsaved if you call this during the "execute_action()" process.
497
498 get_unsaved_history()
499
500 Returns list of all unsaved history objects for this workflow.
501
502 clear_history()
503
504 Clears all transient history objects from the workflow object, not from
505 the long-term storage.
506
507 Properties
508
509 Unless otherwise noted properties are read-only.
510
511 id
512
513 ID of this workflow. This will always be defined, since when the Work‐
514 flow::Factory creates a new workflow it first saves it to long-term
515 storage.
516
517 type
518
519 Type of workflow this is. You may have many individual workflows asso‐
520 ciated with a type.
521
522 description
523
524 Description (usually brief, hopefully with a URL...) of this workflow.
525
526 state
527
528 The current state of the workflow.
529
530 last_update (read-write)
531
532 Date of the workflow's last update.
533
534 context (read-write, see below)
535
536 A Workflow::Context object associated with this workflow. This should
537 never be undefined as the Workflow::Factory sets an empty context into
538 the workflow when it is instantiated.
539
540 If you add a context to a workflow and one already exists, the values
541 from the new workflow will overwrite values in the existing workflow.
542 This is a shallow merge, so with the following:
543
544 $wf->context->param( drinks => [ 'coke', 'pepsi' ] );
545 my $context = Workflow::Context->new();
546 $context->param( drinks => [ 'beer', 'wine' ] );
547 $wf->context( $context );
548 print 'Current drinks: ', join( ', ', @{ $wf->context->param( 'drinks' ) } );
549
550 You will see:
551
552 Current drinks: beer, wine
553
554 Internal Methods
555
556 init( $id, $current_state, \%workflow_config, \@wf_states )
557
558 THIS SHOULD ONLY BE CALLED BY THE Workflow::Factory. Do not call this
559 or the "new()" method yourself -- you will only get an exception. Your
560 only interface for creating and fetching workflows is through the fac‐
561 tory.
562
563 This is called by the inherited constructor and sets the $current_state
564 value to the property "state" and uses the other non-state values from
565 "\%config" to set parameters via the inherited "param()".
566
567 _get_action( $action_name )
568
569 Retrieves the action object associated with $action_name in the current
570 workflow state. This will throw an exception if:
571
572 · No workflow state exists with a name of the current state. (This is
573 usually some sort of configuration error and should be caught at
574 initialization time, so it should not happen.)
575
576 · No action $action_name exists in the current state.
577
578 · No action $action_name exists in the workflow universe.
579
580 · One of the conditions for the action in this state is not met.
581
582 _get_workflow_state( [ $state ] )
583
584 Return the Workflow::State object corresponding to $state, which
585 defaults to the current state.
586
587 _set_workflow_state( $wf_state )
588
589 Assign the Workflow::State object $wf_state to the workflow.
590
591 _get_next_state( $action_name )
592
593 Returns the name of the next state given the action $action_name.
594 Throws an exception if $action_name not contained in the current state.
595
596 #=head3 set
597
599 Workflow::Context
600
601 Workflow::Factory
602
603 Workflow::State
604
605 October 2004 talk 'Workflows in Perl' given to pgh.pm:
606 <http://www.cwinters.com/pdf/workflow_pgh_pm.pdf>
607
609 Copyright (c) 2003 Chris Winters and Arvato Direct; 2004, 2005, 2006
610 Chris Winters. All rights reserved.
611
612 This library is free software; you can redistribute it and/or modify it
613 under the same terms as Perl itself.
614
616 Jonas B. Nielsen (jonasbn) <jonasbn@cpan.org>, current maintainer.
617
618 Chris Winters <chris@cwinters.com>, original author.
619
620 The following folks have also helped out:
621
622 Alexander Klink, for: patches resulting in 0.23, 0.24 and 0.25
623
624 Michael Bell, for patch resulting in 0.22
625
626 Martin Bartosch, for bug reporting and giving the solution not even
627 using a patch (0.19 to 0.20) and a patch resulting in 0.21
628
629 Randal Schwartz, for testing 0.18 and swiftly giving feedback (0.18 to
630 0.19)
631
632 Chris Brown, for a patch to Workflow::Config::Perl (0.17 to 0.18)
633
634 Dietmar Hanisch <Dietmar.Hanisch@Bertelsmann.de> - Provided most of the
635 good ideas for the module and an excellent example of everyday use.
636
637 Tom Moertel <tmoertel@cpan.org> gave me the idea for being able to
638 attach event listeners (observers) to the process.
639
640 Michael Roberts <michael@vivtek.com> graciously released the 'Workflow'
641 namespace on CPAN; check out his Workflow toolkit at
642 <http://www.vivtek.com/wftk.html>.
643
644 Michael Schwern <schwern@pobox.org> barked via RT about a dependency
645 problem and CPAN naming issue.
646
647 Jim Smith <jgsmith@tamu.edu> - Contributed patches (being able to sub‐
648 class Workflow::Factory) and good ideas.
649
650 Martin Winkler <mw@arsnavigandi.de> - Pointed out a bug and a few other
651 items.
652
653
654
655perl v5.8.8 2007-04-25 Workflow(3)