File Coverage

blib/lib/Workflow/Config.pm
Criterion Covered Total %
statement 51 53 96.2
branch 13 16 81.2
condition 1 3 33.3
subroutine 13 13 100.0
pod 5 5 100.0
total 83 90 92.2


line stmt bran cond sub pod time code
1             package Workflow::Config;
2              
3 33     33   1440015 use warnings;
  33         69  
  33         2230  
4 33     33   190 use strict;
  33         65  
  33         869  
5 33     33   372 use v5.14.0;
  33         216  
6 33     33   226 use parent qw( Class::Factory );
  33         154  
  33         337  
7 33     33   130183 use Data::Dumper qw( Dumper );
  33         418487  
  33         7034  
8 33     33   3314 use Workflow::Exception qw( configuration_error );
  33         89  
  33         31468  
9              
10             $Workflow::Config::VERSION = '2.09';
11              
12             # Map the valid type to the top-level XML tag or data
13             # structure to look for.
14             my %VALID_TYPES = (
15             action => 'actions',
16             condition => 'conditions',
17             observer => 'observers',
18             persister => 'persister',
19             validator => 'validators',
20             workflow => 'workflow',
21             );
22              
23             sub is_valid_config_type {
24 256     256 1 701 my ( $class, $type ) = @_;
25 256         1312 return $VALID_TYPES{$type};
26             }
27              
28             sub get_valid_config_types {
29 2     2 1 557 my @keys = sort keys %VALID_TYPES;
30              
31 2         19 return @keys;
32             }
33              
34             sub get_config_type_tag {
35 129     129 1 449 my ( $class, $type ) = @_;
36 129         750 return $VALID_TYPES{$type};
37             }
38              
39             # Class method that allows you to pass in any type of items in
40             # @items. So you can do:
41             #
42             # Workflow::Config->parse_all_files( 'condition', 'my_condition.xml', 'your_condition.perl' );
43              
44             sub parse_all_files {
45 177     177 1 3511 my ( $class, $type, @files ) = @_;
46              
47 177 100       594 return () unless ( scalar @files );
48              
49 176         440 my %parsers = ();
50 176         1129 my %parse_types = map { $_ => 1 } $class->get_registered_types;
  880         5200  
51              
52 176         1347 my @configurations = ();
53              
54 176         428 foreach my $file (@files) {
55 177 100       616 next unless ($file);
56 122         1261 my ($file_type) = $file =~ /\.(\w+)$/;
57 122 100       527 unless ( $parse_types{$file_type} ) {
58 1         15 configuration_error
59             "Cannot parse configuration file '$file' of workflow ",
60             "type '$type'. The file has unknown configuration type ",
61             "'$file_type'; known configuration types are: ", "'",
62             join( ', ', keys %parse_types ), "'";
63             }
64 121 100       433 unless ( $parsers{$file_type} ) {
65 120         661 $parsers{$file_type} = $class->new($file_type);
66             }
67 121         4596 push @configurations, $parsers{$file_type}->parse( $type, $file );
68             }
69 175         1925 return @configurations;
70             }
71              
72             sub parse {
73 1     1 1 1506 my ( $self, $type, @items ) = @_;
74 1   33     12 my $class = ref($self) || $self;
75 1         6 configuration_error "Class $class must implement 'parse()'";
76             }
77              
78             sub _check_config_type {
79 146     146   406 my ( $class, $type ) = @_;
80 146 100       615 unless ( $class->is_valid_config_type($type) ) {
81 1         11 configuration_error "When parsing a configuration file the ",
82             "configuration type (first argument) must be ", "one of: ",
83             join ', ', $class->get_valid_config_types;
84             }
85             }
86              
87             sub _expand_refs {
88 144     144   428 my (@items) = @_;
89 144         354 my @all = ();
90              
91 144 50       448 if ( !scalar @items ) {
92 0         0 return @all;
93             }
94              
95 144         368 foreach my $item (@items) {
96 157 50       458 next unless ($item);
97 157 50       680 push @all, ( ref $item eq 'ARRAY' ) ? @{$item} : $item;
  0         0  
98             }
99 144         521 return @all;
100             }
101              
102             __PACKAGE__->register_factory_type( perl => 'Workflow::Config::Perl' );
103             __PACKAGE__->register_factory_type( pl => 'Workflow::Config::Perl' );
104             __PACKAGE__->register_factory_type( xml => 'Workflow::Config::XML' );
105             __PACKAGE__->register_factory_type( yaml => 'Workflow::Config::YAML' );
106             __PACKAGE__->register_factory_type( yml => 'Workflow::Config::YAML' );
107              
108             1;
109              
110             __END__
111              
112             =pod
113              
114             =head1 NAME
115              
116             Workflow::Config - Parse configuration files for the workflow components
117              
118             =head1 VERSION
119              
120             This documentation describes version 2.09 of this package
121              
122             =head1 SYNOPSIS
123              
124             # Reference multiple files
125              
126             my $parser = Workflow::Config->new( 'xml' );
127             my @config = $parser->parse(
128             'action', 'workflow_action.xml', 'other_actions.xml'
129             );
130              
131             # Read in one of the file contents from somewhere else
132             my $xml_contents = read_contents_from_db( 'other_actions.xml' );
133             my @config = $parser->parse(
134             'action', 'workflow_action.xml', \$xml_contents
135             );
136             _
137             # Reference multiple files of mixed types
138              
139             my @action_config = Workflow::Config->parse_all_files(
140             'action', 'my_actions.xml', 'your_actions.perl'
141             );
142              
143             =head1 DESCRIPTION
144              
145             Read in configurations for the various workflow components. Currently
146             the class understands XML (preferred) and serialized Perl data
147             structures as valid configuration file formats. (I tried to use INI
148             files but there was too much deeply nested information. Sorry.)
149              
150             =head1 CLASS METHODS
151              
152             =head3 parse_all_files( $workflow_config_type, @files )
153              
154             Runs through each file in C<@files> and processes it according to the valid
155              
156             =head1 SUBCLASSING
157              
158             =head2 Creating Your Own Parser
159              
160             If you want to store your configuration in a different format you can
161             create your own parser. All you need to do is:
162              
163             =over 4
164              
165             =item 1.
166              
167             subclass L<Workflow::Config>
168              
169             =item 2.
170              
171             implement the required methods (listed below)
172              
173             =item 3.
174              
175             register your parser with L<Workflow::Config>.
176              
177             =back
178              
179             For instance, if you wanted to use YAML for configuration files you
180             would do something like:
181              
182             # just a convention, you can use any namespace you want
183             package Workflow::Config::YAML;
184              
185             use strict;
186              
187             # Requirement 1: Subclass Workflow::Config
188             use parent qw( Workflow::Config );
189              
190             # Requirement 2: Implement required methods
191             sub parse { ... }
192              
193             The third requirement is registration, which just tells
194             L<Workflow::Config> which parser to use for a particular type. To do
195             this you have two options.
196              
197             B<Registration option one>
198              
199             Register yourself in your own class, adding the following call
200             anywhere the end:
201              
202             # Option 1: Register ourselves by name
203             Workflow::Config->register_factory_type( yaml => 'Workflow::Config::YAML' );
204              
205             Now you just need to include the configuration class in your workflow
206             invocation script:
207              
208             use strict;
209             use Workflow::Factory qw( FACTORY );
210             use Workflow::Config::YAML; # <-- brings in the registration
211              
212             B<Registration option two>
213              
214             You can also just explicitly add the registration from your workflow
215             invocation script:
216              
217             use strict;
218             use Workflow::Factory qw( FACTORY );
219             use Workflow::Config;
220              
221             # Option 2: explicitly register your configuration parser
222             Workflow::Config->register_factory_type( yaml => 'Workflow::Config::YAML' );
223              
224             Whichever one you choose you can now parse (in this example) YAML
225             files alongside the built-in parsers for XML and Perl files:
226              
227             FACTORY->add_config_from_file(
228             workflow => 'workflow.yaml',
229             action => [ 'my_actions.yaml', 'other_actions.xml' ],
230             validator => 'validators.yaml',
231             condition => [ 'my_conditions.yaml', 'other_conditions.xml' ]
232             persister => 'persister.perl',
233             );
234              
235             =head2 Inherited Methods
236              
237             =head3 new( $parser_type )
238              
239             Instantiates an object of the correct type -- see L<Class::Factory>
240             for how this is implemented:
241              
242             # Parser of type 'Workflow::Config::XML'
243             my $xml_parser = Workflow::Config->new( 'xml' );
244              
245             # Parser of type 'Workflow::Config::Perl
246             my $perl_parser = Workflow::Config->new( 'perl' );
247              
248             =head3 is_valid_config_type( $config_type )
249              
250             Returns true if C<$config_type> is a valid configuration type, false
251             if not. Valid configuration types are: 'action', 'condition',
252             'validator', 'workflow'.
253              
254             =head3 get_valid_config_types()
255              
256             Returns list of strings representing the valid configuration types.
257              
258             =head3 get_config_type_tag( $class, $type )
259              
260             Returns string representing a valid configuration type, looking up the type
261             parameter in a lookuptable defined in Workflow::Config class.
262              
263             =head2 Required Object Methods
264              
265             =head3 parse( $workflow_config_type, @items )
266              
267             Parse each item in C<@items> to a hash reference based on the
268             configuration type C<$config_type> which must pass the
269             C<is_valid_config_type()> test. An 'item' is either a filename or a
270             scalar reference with the contents of a file. (You can mix and match
271             as seen in the L<SYNOPSIS>.)
272              
273             Should throw an exception if:
274              
275             =over 4
276              
277             =item *
278              
279             You pass an invalid workflow configuration type. Valid workflow
280             configuration types are registered in L<Workflow::Config> and are
281             available from C<get_valid_config_types()>; you can check whether a
282             particular type is valid with C<is_valid_config_type()>. (See above
283             for descriptions.)
284              
285             =item *
286              
287             You pass in a file that cannot be read or parsed because of
288             permissions, malformed XML, incorrect Perl data structure, etc. It
289             does B<not> do a validation check (e.g., to ensure that every 'action'
290             within a workflow state has a 'resulting_state' key).
291              
292             =back
293              
294             Returns: one hash reference for each member of C<@items>
295              
296             =head1 CONFIGURATION INFORMATION
297              
298             This gives you an idea of the configuration information in the various
299             workflow pieces:
300              
301             =head2 workflow
302              
303             workflow
304             class $
305             type $
306             description $
307             persister $
308             initial_state $
309             observer \@
310             sub $
311             class $
312             state \@
313             name $
314             description $
315             action \@
316             name $
317             resulting_state $ -or- \%
318             condition \@
319             name $
320              
321             =over 4
322              
323             =item *
324              
325             the 'class', 'type' and 'description' keys are at the top level
326              
327             =item *
328              
329             'persister' key holds a string declaring the name of a persister
330             as declared in the array of persisters
331              
332             =item *
333              
334             'initial_state' key holds a string declaring the name of the initial state.
335             by default, this value is 'INIITAL'.
336              
337             =item *
338              
339             'state' key holds array of one or more 'state' declarations; one of
340             them must be 'INITIAL' (or the value of initial_state, if it's defined)
341              
342             =item *
343              
344             each 'state' declaration holds 'description' and 'name' keys and
345             multiple 'action' declarations
346              
347             =item *
348              
349             each 'action' declaration holds 'name' and 'resulting_state' keys and
350             may hold a 'condition' key with one or more named conditions
351              
352             The value of the 'resulting_state' key may either be a string (the name
353             of the single next state) or a hash mapping return values of the action
354             to next states. The C< * > (asterisk) is the catch-all value mapping all
355             unspecified values to a single next state.
356              
357             =item *
358              
359             each 'observer' names either a C<sub> or a C<class>. The C<sub> should
360             have a package prefix (C<Package::subname>). When a C<class> name is
361             given, the C<update> sub is called as a class method in the given C<class>.
362              
363             =back
364              
365             =head2 condition
366              
367             conditions:
368              
369             type $
370             condition \@
371             name $
372             class $
373             param \@
374             name $
375             value $
376              
377             =over 4
378              
379             =item *
380              
381             array of one or more hashrefs with 'name' and 'class' keys
382              
383             =back
384              
385             =head2 validator
386              
387             validators:
388              
389             validator \@
390             name $
391             class $
392             param \@
393             name $
394             value $
395              
396             =over 4
397              
398             =item *
399              
400             array of one or more hashrefs with 'name' and 'class' keys, plus
401             possibly one or more 'param' hashrefs each with 'name' and 'value'
402             keys
403              
404             =back
405              
406             =head2 action
407              
408             actions:
409              
410             type $
411             action \@
412             name $
413             class $
414             description $
415             field \@
416             name $
417             is_required yes|no
418             type $
419             source_list \@ of $
420             source_class $
421             param \@
422             name $
423             value $
424             validator \@
425             name $
426             arg \@
427             value $
428              
429             =over 4
430              
431             =item *
432              
433             array of one or more action hashrefs with 'name', 'class' and
434             'description' keys
435              
436             =item *
437              
438             each 'action' may specify a 'type' (default value: 'default'); in case
439             a workflow specifies a 'type', actions specifying the same 'type' will
440             be preferred over actions with the 'default' type when multiple actions
441             by the same name exist.
442              
443             =item *
444              
445             each 'action' may have zero or more values used to fill it; each value
446             has a 'name', 'description' and 'necessity' ('required' or 'optional')
447              
448             =item *
449              
450             each 'action' may have any number of 'param' hashrefs, each with
451             'name' and 'value'
452              
453             =item *
454              
455             each 'action' may have any number of 'validator' hashrefs, each with a
456             'name' key and array of 'arg' declarations
457              
458             =back
459              
460             =head2 persister
461              
462             persister:
463             name $ # all persister classes
464             class $ # all persister classes
465             use_random yes|no # all persister classes
466             use_uuid yes|no # all persister classes
467              
468             driver $ # DBI persisters
469             options \@ # DBI persisters
470             name $ # DBI persisters
471             value $ # DBI persisters
472             dsn $ # DBI persisters
473             user $ # DBI persisters
474             password $ # DBI persisters
475             workflow_table $ # DBI persisters
476             history_table $ # DBI persisters
477             autocommit $ # DBI persisters
478             date_format $ # DBI persisters
479              
480             table $ # DBI/ExtraData persisters
481             data_field $ # DBI/ExtraData persisters
482             context_key $ # DBI/ExtraData persisters
483              
484             path $ # File persisters
485              
486             =over 4
487              
488             =item *
489              
490             'name' key holds a string declaring the name by which workflows may
491             refer to this persister configuration
492              
493             =item *
494              
495             'class' key names a Perl class name to use when instantiating the persister
496              
497             =item *
498              
499             'use_random' key holds a string declaring (through 'yes'/'no' value) to
500             use random values for the workflow identifier
501              
502             =item *
503              
504             'use_uuid' key holds a string declaring (through 'yes'/'no' value) to
505             use UUID (universally unique ID) values for the workflow identifier; UUIDs
506             take preference over random IDs
507              
508             =back
509              
510             For documentation of the other keys, please refer to the respective classes.
511              
512             =head2 observer
513              
514             observers:
515              
516             observer \@
517             name $
518             type $
519             sub $
520             class $
521              
522             =over 4
523              
524             =item *
525              
526             C<name> specifies the name of the observer for debugging purposes.
527              
528             =item *
529              
530             C<type> names the type of workflow to apply the observer to. The default
531             value is C<default>.
532              
533             =item *
534              
535             The C<sub> and C<class> values have the same specification as given for
536             the workflow observer keys.
537              
538             =back
539              
540              
541             =head1 COPYRIGHT
542              
543             Copyright (c) 2003-2021 Chris Winters. All rights reserved.
544              
545             This library is free software; you can redistribute it and/or modify
546             it under the same terms as Perl itself.
547              
548             Please see the F<LICENSE>
549              
550             =head1 AUTHORS
551              
552             Please see L<Workflow>
553              
554             =cut