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