File Coverage

lib/Workflow/Action.pm
Criterion Covered Total %
statement 88 94 93.6
branch 6 10 60.0
condition n/a
subroutine 16 17 94.1
pod 9 9 100.0
total 119 130 91.5


line stmt bran cond sub pod time code
1              
2             # Note: we may implement a separate event mechanism so that actions
3             # can trigger other code (to read observations from database?)
4              
5             use warnings;
6 20     20   618371 use strict;
  20         46  
  20         710  
7 20     20   113 use base qw( Workflow::Base );
  20         38  
  20         622  
8 20     20   113 use Log::Log4perl qw( get_logger );
  20         39  
  20         2675  
9 20     20   142 use Workflow::Action::InputField;
  20         38  
  20         144  
10 20     20   6700 use Workflow::Validator::HasRequiredField;
  20         40  
  20         119  
11 20     20   6640 use Workflow::Factory qw( FACTORY );
  20         41  
  20         165  
12 20     20   1697 use Carp qw(croak);
  20         38  
  20         85  
13 20     20   664  
  20         32  
  20         16685  
14             $Workflow::Action::VERSION = '1.61';
15              
16             my @PROPS = qw( name class description group );
17             my @INTERNAL = qw( _factory );
18             __PACKAGE__->mk_accessors( @PROPS, @INTERNAL );
19              
20             ####################
21             # INPUT FIELDS
22              
23             my ( $self, @fields ) = @_;
24             push @{ $self->{_fields} }, @fields;
25 71     71 1 133 }
26 71         85  
  71         222  
27             my ($self) = @_;
28             return grep { $_->requirement() eq 'required' } @{ $self->{_fields} };
29             }
30 60     60 1 94  
31 60         64 my ($self) = @_;
  71         482  
  60         132  
32             return grep { $_->requirement() eq 'optional' } @{ $self->{_fields} };
33             }
34              
35 0     0 1 0 my ($self) = @_;
36 0         0 return @{ $self->{_fields} };
  0         0  
  0         0  
37             }
38              
39             ####################
40 1     1 1 3 # VALIDATION
41 1         1  
  1         14  
42             my ( $self, @validator_info ) = @_;
43             my @validators = ();
44             foreach my $conf (@validator_info) {
45             my $validator = $self->_factory()->get_validator( $conf->{name} );
46             my @args = $self->normalize_array( $conf->{arg} );
47             push @validators,
48 60     60 1 91 {
49 60         74 validator => $validator,
50 60         86 args => \@args
51 13         44 };
52 13         52 }
53 13         51 push @{ $self->{_validators} }, @validators;
54             }
55              
56             my ($self) = @_;
57             return () if ( not defined $self->{_validators} );
58             return @{ $self->{_validators} };
59 60         75 }
  60         108  
60              
61             my ( $self, $wf ) = @_;
62             my @validators = $self->get_validators;
63 53     53 1 76 return unless ( scalar @validators );
64 53 50       117  
65 53         80 my $context = $wf->context;
  53         133  
66             foreach my $validator_info (@validators) {
67             my $validator = $validator_info->{validator};
68             my $args = $validator_info->{args};
69 53     53 1 78  
70 53         133 # TODO: Revisit this statement it does not look right
71 53 50       101 # runtime_args becomes the WF object??
72             my @runtime_args = ($wf);
73 53         170 foreach my $arg ( @{$args} ) {
74 53         84 if ( $arg =~ /^\$(.*)$/ ) {
75 62         79 push @runtime_args, scalar $context->param($1);
76 62         73 } else {
77             push @runtime_args, $arg;
78             }
79             }
80 62         83 $validator->validate(@runtime_args);
81 62         63 }
  62         85  
82 45 100       158 }
83 9         27  
84             # Subclasses override...
85 36         57  
86             my ( $self, $wf ) = @_;
87             croak "Class ", ref($self), " must implement 'execute()'\n";
88 62         200 }
89              
90             ########################################
91             # PRIVATE
92              
93             my ( $self, $wf, $params ) = @_;
94              
95 1     1 1 278 # So we don't destroy the original...
96 1         13 my %copy_params = %{$params};
97              
98             $self->_factory( $wf->_factory() );
99             $self->class( $copy_params{class} );
100             $self->name( $copy_params{name} );
101             $self->description( $copy_params{description} );
102             $self->group( $copy_params{group} );
103 61     61 1 147  
104             ## init normal fields
105             my @fields = $self->normalize_array( $copy_params{field} );
106 61         82 foreach my $field_info (@fields) {
  61         230  
107             if ( my $field_class = $field_info->{class} ) {
108 60         151 $self->log->debug("Using custom field class $field_class");
109 60         1375 $self->add_fields( $field_class->new($field_info) );
110 60         603 } else {
111 60         601 $self->log->debug("Using standard field class");
112 60         671 $self->add_fields(
113             Workflow::Action::InputField->new($field_info) );
114             }
115 60         638 }
116 60         116  
117 71 50       176 ## establish validator for fields with is_required="yes"
118 0         0 @fields = $self->required_fields();
119 0         0 my $validator = Workflow::Validator::HasRequiredField->new(
120             { name => 'HasRequiredField for is_required fields',
121 71         256 class => 'Workflow::Validator::HasRequiredField'
122 71         25967 }
123             );
124             my @args = ();
125             foreach my $field (@fields) {
126             next if ( not $field ); ## empty @fields array
127             push @args, $field->name();
128 60         259 }
129 60         582 push @{ $self->{_validators} },
130             {
131             validator => $validator,
132             args => \@args
133             };
134 60         133  
135 60         113 ## init normal validators
136 58 50       450 my @validator_info = $self->normalize_array( $copy_params{validator} );
137 58         121 $self->add_validators(@validator_info);
138              
139 60         179 delete @copy_params{(@PROPS, qw( field validator ))};
  60         302  
140              
141             # everything else is just a passthru param
142              
143             while ( my ( $key, $value ) = each %copy_params ) {
144             $self->param( $key, $value );
145             }
146 60         208 }
147 60         228  
148             1;
149 60         185  
150              
151             =pod
152              
153 60         259 =head1 NAME
154 3         17  
155             Workflow::Action - Base class for Workflow actions
156              
157             =head1 VERSION
158              
159             This documentation describes version 1.61 of this package
160              
161             =head1 SYNOPSIS
162              
163             # Configure the Action...
164             <action name="CreateUser"
165             class="MyApp::Action::CreateUser">
166             <field name="username" is_required="yes"/>
167             <field name="email" is_required="yes"/>
168             <validator name="IsUniqueUser">
169             <arg>$username</arg>
170             </validator>
171             <validator name="IsValidEmail">
172             <arg>$email</arg>
173             </validator>
174             </action>
175              
176             # Define the action
177              
178             package MyApp::Action::CreateUser;
179              
180             use base qw( Workflow::Action );
181             use Workflow::Exception qw( workflow_error );
182              
183             sub execute {
184             my ( $self, $wf ) = @_;
185             my $context = $wf->context;
186              
187             # Since 'username' and 'email' have already been validated we
188             # don't need to check them for uniqueness, well-formedness, etc.
189              
190             my $user = eval {
191             User->create({ username => $context->param( 'username' ),
192             email => $context->param( 'email' ) })
193             };
194              
195             # Wrap all errors returned...
196              
197             if ( $@ ) {
198             workflow_error
199             "Cannot create new user with name '", $context->param( 'username' ), "': $EVAL_ERROR";
200             }
201              
202             # Set the created user in the context for the application and/or
203             # other actions (observers) to use
204              
205             $context->param( user => $user );
206              
207             # return the username since it might be used elsewhere...
208             return $user->username;
209             }
210              
211             =head1 DESCRIPTION
212              
213             This is the base class for all Workflow Actions. You do not have to
214             use it as such but it is strongly recommended.
215              
216             =head1 CONFIGURATION
217              
218             You configure your actions and map them to a specific module in your actions
219             configuration file using the syntax
220             above and that shown in L<Workflow>. In some cases, you'll have actions
221             that apply to all workflows. In more elaborate configurations, you may have
222             one workflow server loading multiple workflows and multiple actions for each.
223             In these
224             cases, you'll have multiple workflow types and you may want actions
225             with the same names to have different behaviors for each type.
226              
227             For example, you may have a workflow type Ticket and another type Order_Parts.
228             They both may have a Submit action, but you'll want the Submit to be different
229             for each.
230              
231             You can specify a type in your actions configuration to associate that action
232             with that workflow type. If you don't provide a type, the action is available
233             to all types. For example:
234              
235             <actions>
236             <type>Ticket</type>
237             <description>Actions for the Ticket workflow only.</description>
238             <action name="TIX_NEW"
239             class="TestApp::Action::TicketCreate">
240             ...Addtional configuration...
241              
242             The type must match an existing workflow type or the action will never
243             be called.
244              
245             =head1 STANDARD ATTRIBUTES
246              
247             Each action supports the following attributes:
248              
249             =over
250              
251             =item * C<class>
252              
253             The Perl class which provides the behaviour of the action.
254              
255             =item * C<description>
256              
257             A free text field describing the action.
258              
259             =item * C<group>
260              
261             The group for use with the L<Workflow::State/get_available_action_names>
262             C<$group> filter.
263              
264             =item * C<name>
265              
266             The name by which workflows can reference the action.
267              
268             =item * C<type>
269              
270             Associates the action with workflows of the same type, when set. When
271             not set, the action is available to all workflows.
272              
273             =back
274              
275              
276             These attributes (except for the C<class> attribute) all map to instance
277             properties by the same name.
278              
279              
280             =head1 ADDITIONAL ATTRIBUTES
281              
282             You can validate additional attributes in of your action by doing two things:
283              
284             =over
285              
286             =item *
287              
288             Set C<$Workflow::Factory::VALIDATE_ACTION_CONFIG> to 1.
289              
290             =item *
291              
292             Provide function validate_config() in your action class.
293              
294             =back
295              
296             Then, this function will be called with all the acton attributes when
297             it is parsed. For example, if your action XML looks like this:
298              
299             <action name="BEGIN" class="My::Class" when="NOW">
300              
301             You can validate it like this:
302              
303             sub My::Class::validate_config {
304             my $config = shift;
305             unless ('NOW' eq $config->{when}) {
306             configuration_error "`$$config{when}' is not a valid value " .
307             "for `when'";
308             }
309             }
310              
311             =head1 OBJECT METHODS
312              
313             =head2 Public Methods
314              
315             =head3 new()
316              
317             Subclasses may override this method, but it's not very common. It is
318             called when you invoke a method in your Workflow object that returns
319             an Action object, for example, methods such as $wf->get_action will
320             call this method.
321              
322             B<Your action classes usually subclass directly from Workflow::Action
323             and they I<don't> need to override this method at all>. However, under
324             some circumstances, you may find the need to extend your action
325             classes.
326              
327             =head3 init()
328              
329             Suppose you want to define some extra properties to actions but you
330             also want for some of these properties to depend on a particular
331             state. For example, the action "icon" will almost allways be the same,
332             but the action "index" will depend on state, so you can display your
333             actions in a certain order according to that particular state. Here is
334             an example on how you easily do this by overriding new():
335              
336             1) Set the less changing properties in your action definition:
337              
338             <actions>
339             <type>foo</type>
340             <action name="Browse"
341             type="menu_button" icon="list_icon"
342             class="actual::action::class">
343             </action>
344              
345             2) Set the state dependant properties in the state definition:
346              
347             <state name="INITIAL">
348             <description>
349             Manage Manufaturers
350             </description>
351             <action index="0" name="Browse" resulting_state="BROWSE">
352             <condition name="roleis_oem_mgmt"/>
353             </action>
354             <action index="1" name="Create" resulting_state="CREATE">
355             <condition name="roleis_oem_mgmt"/>
356             </action>
357             <action index="2" name="Back" resulting_state="CLOSED"/>
358             </state>
359              
360             3) Craft a custom action base class
361              
362             package your::action::base::class;
363              
364             use warnings;
365             use strict;
366              
367             use base qw( Workflow::Action );
368             use Workflow::Exception qw( workflow_error );
369              
370             # extra action class properties
371             my @EXTRA_PROPS = qw( index icon type data );
372             __PACKAGE__->mk_accessors(@EXTRA_PROPS);
373              
374             sub init {
375             my ($self, $wf, $params) = @_;
376             $self->SUPER::init($wf, $params);
377             # set only our extra properties from action class def
378             foreach my $prop (@EXTRA_PROPS) {
379             next if ( $self->$prop );
380             $self->$prop( $params->{$prop} );
381             }
382             # override specific extra action properties according to state
383             my $wf_state = $wf->_get_workflow_state;
384             my $action = $wf_state->{_actions}->{$self->name};
385             $self->index($action->{index});
386             }
387              
388              
389             1;
390              
391             4) Use your custom action base class instead of the default
392              
393             package actual::action::class;
394              
395             use warnings;
396             use strict;
397              
398             use base qw( your::base::action::class );
399             use Workflow::Exception qw( workflow_error );
400              
401             sub execute {
402             ...
403             }
404              
405             1;
406              
407              
408             =head3 required_fields()
409              
410             Return a list of L<Workflow::Action::InputField> objects that are required.
411              
412             =head3 optional_fields()
413              
414             Return a list of L<Workflow::Action::InputField> objects that are optional.
415              
416             =head3 fields()
417              
418             Return a list of all L<Workflow::Action::InputField> objects
419             associated with this action.
420              
421              
422             =head2 Private Methods
423              
424             =head3 init( $workflow, \%params )
425              
426             init is called in conjuction with the overall workflow initialization.
427              
428             It sets up the necessary validators based on the on configured actions, input fields and required fields.
429              
430             =head3 add_field( @fields )
431              
432             Add one or more L<Workflow::Action::InputField>s to the action.
433              
434             =head3 add_validators( @validator_config )
435              
436             Given the 'validator' configuration declarations in the action
437             configuration, ask the L<Workflow::Factory> for the
438             L<Workflow::Validator> object associated with each name and store that
439             along with the arguments to be used, runtime and otherwise.
440              
441             =head3 get_validators()
442              
443             Get a list of all the validator hashrefs, each with two keys:
444             'validator' and 'args'. The 'validator' key contains the appropriate
445             L<Workflow::Validator> object, while 'args' contains an arrayref of
446             arguments to pass to the validator, some of which may need to be
447             evaluated at runtime.
448              
449             =head3 validate( $workflow )
450              
451             Run through all validators for this action. If any fail they will
452             throw a L<Workflow::Exception>, the validation subclass.
453              
454             =head3 execute( $workflow )
455              
456             Subclasses B<must> implement -- this will perform the actual
457             work. It's not required that you return anything, but if the action
458             may be used in a L<Workflow::State> object that has multiple resulting
459             states you should return a simple scalar for a return value.
460              
461             =head3 add_fields
462              
463             Method to add fields to the workflow. The method takes an array of
464             fields.
465              
466             =head1 SEE ALSO
467              
468             =over
469              
470             =item * L<Workflow>
471              
472             =item * L<Workflow::Factory>
473              
474             =back
475              
476             =head1 COPYRIGHT
477              
478             Copyright (c) 2003-2022 Chris Winters. All rights reserved.
479              
480             This library is free software; you can redistribute it and/or modify
481             it under the same terms as Perl itself.
482              
483             Please see the F<LICENSE>
484              
485             =head1 AUTHORS
486              
487             Please see L<Workflow>
488              
489             =cut