File Coverage

lib/Workflow/Config.pm
Criterion Covered Total %
statement 52 54 96.3
branch 13 16 81.2
condition 1 3 33.3
subroutine 13 13 100.0
pod 5 5 100.0
total 84 91 92.3


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