File Coverage

blib/lib/Application/Pipeline.pm
Criterion Covered Total %
statement 47 117 40.1
branch 5 44 11.3
condition 0 32 0.0
subroutine 11 18 61.1
pod 9 9 100.0
total 72 220 32.7


line stmt bran cond sub pod time code
1             package Application::Pipeline;
2             $VERSION = '0.1';
3              
4             =head1 Application::Pipeline
5              
6             Application::Pipeline is a module designed to map methods ( referred to in this
7             role as handlers ) to different phases of an application's life cycle.
8             By assigning methods to different phases of this pipeline, the author can
9             concentrate on the logic for each phase and let the framework manage the
10             application flow. Adopting the same idea as CGI::Application, writing an
11             application with Application::Pipeline is a matter of creating a module that is
12             a subclass of Application::Pipeline.
13              
14             =head2 The %plan
15              
16             To build a pipeline application, it is necessary to register methods to run
17             during each phase. This can be done one at a time, with the C
18             method. But Application::Pipeline also looks in the subclass package for the
19             package variable C<%plan>. This hash's keys are the names of the phases of the
20             pipeline. Each key points to an array reference which is a list of the methods
21             to run for that phase. The methods are either the names of the methods to run,
22             or references to the actual methods.
23              
24             This is not the be-all end-all definition of the pipeline. It is still possible
25             to use C to modify the pipeline, and as explained later, it is
26             possible to take into account C<%plan>s defined in superclasses.
27              
28             =cut
29              
30             #-- pragmas ----------------------------
31 3     3   96815 use strict;
  3         10  
  3         165  
32 3     3   27 use warnings;
  3         10  
  3         446  
33              
34             #-- modules ----------------------------
35 3     3   6039 use Symbol qw( gensym );
  3         3861  
  3         3709  
36              
37             #-- package variables ------------------
38              
39             our @phase_stages = qw( FIRST EARLY MIDDLE LATE LAST );
40              
41             #===============================================================================
42              
43             =head2 Running an Application::Pipeline Application
44              
45             Application::Pipeline is an object oriented module, but has no constructor. It
46             is intended to be used as a base class only. The primary responsibility of
47             a constructor under Application::Pipeline is to specify an ordered list of
48             names for the phases of the pipeline. As it would be impractical to come up
49             with one unified set of phases that suited every kind of application that
50             Application::Pipeline could drive, that task, and the constructor along with it,
51             is left to subclasses.
52              
53             It is expected that there may eventually become a set of first-level subclasses
54             that define common sets of phases for different problem spaces. This way
55             plugins that are suited to those domains may expect to find a predictable set of
56             phases when included. For an initial example of one such subclass, see
57             WWW::Pipeline.
58              
59             =head3 run
60              
61             $pipeline->run()
62              
63             A script calls this method when it is ready to run the application. There are
64             no parameters
65              
66             =cut
67              
68             sub run {
69 0     0 1 0 my $self = shift;
70              
71 0         0 $self->_buildPlan();
72              
73 0         0 foreach my $phase ( @{$self->{_phases}} ) {
  0         0  
74 0         0 foreach my $stage ( @phase_stages ) {
75              
76 0         0 next unless defined $self->{_plan}{$phase}{$stage}
77 0 0 0     0 and scalar @{$self->{_plan}{$phase}{$stage}};
78              
79 0         0 foreach my $method ( @{$self->{_plan}{$phase}{$stage}} ) {
  0         0  
80 0 0       0 if( $self->can($method) ) {
81 0         0 $self->$method()
82             }
83             else{
84 0         0 eval { $self->$method() };
  0         0  
85 0 0       0 die "Error executing $method for $stage of $phase: $@" if $@;
86             }
87             }
88             }
89             }
90             }
91              
92             #===============================================================================
93              
94             =head2 Building an Application
95              
96             Below are functions most useful when actually writing a Application::Pipeline
97             subclassed application.
98              
99             =head3 setPhases
100              
101             $self->setPhases( qw( Initialization Main Teardown ) );
102              
103             This method is typically invoked during a subclass's constructor to tell
104             Application::Pipeline what phases it will be running. If it is not called
105             before the C method is invoked, the application will simply terminate
106             without having done anything.
107              
108             =cut
109              
110             sub setPhases {
111              
112 1     1 1 19 my( $self, @phases ) = @_;
113 1         8 $self->{_phases} = \@phases;
114 1         10 return 1;
115             }
116              
117             #-------------------------------------------------------------------------------
118              
119             =head3 addHandler
120              
121             $self->addHandler( $phase, $handler, $stage )
122              
123             Registers the given C<$handler> as a method to be run during C<$phase>. The
124             optional C<$stage> parameter specifies where along the phase the method is to
125             be run. Valid Phases are:
126              
127             Initialization ParseRequest GenerateResponse SendResponse Teardown
128              
129             Valid stages are:
130              
131             FIRST EARLY MIDDLE LATE LAST
132              
133             When no stage is specified, C is assumed. $handler may either be the name
134             of a method, or a code reference. Passing a name allows subclasses of the
135             application to override the method, while code references are slightly faster.
136             This is a trick taken directly from the CGI::Application folks.
137              
138             B Use the C and C stages sparingly. Note that each time a
139             handler is added to the stage of a phase, it is added to the end of that stage.
140             C and C are best used for handlers that are depended on by others,
141             but that do not themselves have dependencies.
142              
143             =cut
144              
145             sub addHandler {
146 0     0 1 0 my( $self, $phase, $handler, $stage ) = @_;
147              
148 0 0 0     0 warn "no phases established" and return undef
149             unless defined $self->{_phases};
150              
151 0         0 warn "unrecognized phase '$phase' for handler" and return undef
152 0 0 0     0 unless grep { $phase eq $_ } @{$self->{_phases}};
  0         0  
153              
154 0         0 warn "unrecognized phase stage '$stage'" and return undef
155 0 0 0     0 if $stage and not grep { $stage eq $_ } @phase_stages;
      0        
156              
157 0   0     0 $stage ||= 'MIDDLE';
158 0   0     0 $self->{_plan}{$phase}{$stage} ||= [];
159 0         0 push @{$self->{_plan}{$phase}{$stage}},$handler;
  0         0  
160              
161             }
162              
163             #-------------------------------------------------------------------------------
164              
165             =head3 setPluginLocations
166              
167             $self->setPluginLocations( qw(
168             Application::Pipeline::Services
169             WWW::Pipeline::Services
170             ));
171              
172             It is possible to load plugins from certain predetermined namespaces in such
173             a way that you don't have to specify the fully qualified namespace. After this
174             method is called, any time a plugin is loaded it first will see if that plugin
175             exists by concatenating its name with the namespaces you provided, in the order
176             in which you provided them. Failing that, it will see if the plugin has a fully
177             qualified package name before giving up on loading.
178              
179             =cut
180              
181             sub setPluginLocations {
182 1     1 1 758 my( $self, @locations ) = @_;
183              
184 1         26 $self->{_plugin_locations} = [
185             grep /^[A-Za-z_]\w*(::[A-Za-z_]\w*)*$/, @locations
186             ];
187             }
188              
189             #-------------------------------------------------------------------------------
190              
191             =head3 loadPlugin
192              
193             $self->loadPlugin( $package, @arguments )
194              
195             Takes and tries to load the provided C<$package>. unless the
196             C<$nonstandard_namespace> flag is set it will assume the package needs the
197             'Application::Pipeline::Services' namespace appended to it. Upon requiring the module
198             it passes \@arguments to the package's C method. For more information
199             refer to the section below on writing plugins.
200              
201             =cut
202              
203             sub loadPlugin {
204              
205 0     0 1 0 my( $self, $plugin, @args ) = @_;
206 0 0       0 return 1 if defined $self->{_plugins}{$plugin};
207              
208 0 0       0 if( $self->{_plugin_locations} ) {
209 0         0 foreach my $namespace ( @{$self->{_plugin_locations}}, '' ) {
  0         0  
210 0 0       0 my $package = $namespace
211             ? $namespace .'::'.$plugin
212             : $plugin;
213              
214 0         0 eval "require $package";
215            
216 0 0 0     0 die $@ if $@ and $@ !~ /^Can't locate/;
217 0 0       0 next if $@;
218              
219 0 0 0     0 warn "Nothing to load from $plugin" and return undef
220             unless UNIVERSAL::can( $package, 'load' );
221              
222 0 0 0     0 $package->load( $self, @args )
223             or warn "Failed to load plugin '$plugin'" and return undef;
224 0         0 $self->{_plugins}{$plugin} = $package;
225              
226 0         0 return 1;
227             }
228             }
229              
230 0         0 warn "Failed to load plugin '$plugin': could not locate file";
231 0         0 return undef;
232             }
233              
234             #-------------------------------------------------------------------------------
235              
236             =head3 loadPlugins
237              
238             $self->loadPlugins( 'Foo','Bar',....)
239            
240             A shortcut method for loading plugins that take no arguments.
241              
242             =cut
243              
244             sub loadPlugins {
245 0     0 1 0 my( $self, @packages ) = @_;
246              
247 0         0 my $success_count = 0;
248 0         0 $success_count += $self->loadPlugin($_) foreach @packages;
249 0         0 return $success_count;
250             }
251              
252             #-------------------------------------------------------------------------------
253              
254             =head3 unloadPlugins
255              
256             $self->unloadPlugins( 'Foo','Bar',...)
257              
258             While it will likely be rare that an application would want to manually remove
259             a plugin before it is finished running, this method will do just that to the
260             named plugins. It does so by calling the plugin's package's C method
261             if one exists, and deleting the plugin from the application's registry.
262              
263             =cut
264              
265             sub unloadPlugins {
266 0     0 1 0 my( $self, @packages ) = @_;
267              
268 0         0 foreach my $plugin ( @packages ) {
269 0 0       0 if( my $package = $self->{_plugins}{$plugin} ) {
270 0 0       0 $package->unload( $self ) if UNIVERSAL::can($package, 'unload');
271 0         0 delete $self->{_plugins}{$plugin};
272             }
273             }
274             }
275              
276             #===============================================================================
277              
278             =head2 Writing Plugins
279              
280             Plugins in their simplest form are packages which have two methods: C
281             and, optionally, C. The former is called when the application calls
282             C, and the latter on C
283              
284             C receives the plugin's package name, a reference to the application, and
285             whatever arguments may have been sent through C.
286              
287             C receives the plugin's package name and a reference to the application.
288              
289             =head3 addServices
290              
291             $pipeline->addServices( name => $object, name2 => sub{ },... );
292              
293             This method is most commonly used within the C method of a plugin.
294             Services are either a subroutine that the application will adopt as one of its
295             own by the name specified, or a data structure (often objects) that the
296             application will make available under the specified name
297              
298             =cut
299              
300             sub addServices {
301 3     3 1 1227 my( $self, %services ) = @_;
302              
303 3         18 while( my( $name, $service ) = each %services ) {
304 7 100       145 next unless $name =~ /^[A-Za-z_]\w*$/;
305             {
306 3     3   114 no strict 'refs';
  3         9  
  3         572  
  6         7  
307 6         35 *{ ref($self)."::$name" } = ( ref $service eq 'CODE' )
308             ? $service
309 6 100   5   32 : sub { return $service };
  5         26  
310             }
311 6         47 $self->{_services}{$name} = 1;
312             }
313 3         22 return 1;
314             }
315              
316             #-------------------------------------------------------------------------------
317              
318             =head3 dropServices
319              
320             $pipeline->dropServices( name, name2,... )
321              
322             Probably a good idea to unregister the services you added as a plugin when your
323             C method gets called. Also, rather than forcing the application to
324             manually call your unload method you may choose to register one of your methods
325             as a handler to be run during the Teardown phase of the application, so that you
326             can perform any cleanup you might require.
327              
328             =cut
329              
330             sub dropServices {
331 1     1 1 4 my( $self, @services ) = @_;
332              
333 1         3 foreach my $service ( @services ) {
334 1 50       11 next unless defined $self->{_services}{$service};
335            
336 1         2 my $old;
337             {
338 3     3   21 no strict 'refs';
  3         7  
  3         331  
  1         3  
339 1         3 $old = \*{ ref($self)."::$service" };
  1         7  
340             }
341            
342 1         7 my $new = gensym;
343 1         19 *$new = *$old{$_} foreach ( grep { defined *$old{$_} } qw( SCALAR ARRAY HASH IO FORMAT ) );
  5         18  
344             {
345 3     3   21 no strict 'refs';
  3         10  
  3         1525  
  1         2  
346 1         3 *{ ref($self)."::$service" } = *$new;
  1         11  
347             }
348              
349 1         11 delete $self->{_services}{$service};
350             }
351 1         8 return 1;
352             }
353              
354             #===============================================================================
355              
356             =head2 Building the Pipeline Plan
357              
358             When the run method gets called, the first thing the method does is build the
359             list of methods to be run. First, it checks for a C<%plan> varaible in the
360             current package.
361              
362             Any method entry in a phase of the plan with the value of 'SUPER' causes the
363             application to go looking up the inheritence tree for packages (that are
364             themselves descendents of Application::Pipeline) that have a %plan, and substitutes the
365             superclass' plan for that phase in the place of the 'SUPER' placeholder. This
366             allows the designer of the current application to choose where and whether to
367             include the plan of a superclass for a given phase.
368              
369             The appliation will put all of the plan methods found into the C stage
370             of the phase.
371              
372             =cut
373              
374             sub _buildPlan {
375 0     0     my $self = shift;
376              
377 0   0       $self->{_phases} ||= [];
378 0           foreach my $phase ( @{$self->{_phases}} ) {
  0            
379 0   0       $self->{_plan}{$phase}{MIDDLE} ||= [];
380 0           $self->_buildPhase( $phase, ref $self );
381             }
382             }
383              
384             #-------------------------------------------------------------------------------
385             sub _buildPhase{
386 0     0     my( $self, $phase, @packages ) = @_;
387              
388 0           foreach my $package( @packages ) {
389 0           my %plan = eval '%'.$package .'::plan';
390 0           my @isa = grep { UNIVERSAL::isa($_,'Application::Pipeline') }
  0            
391             eval '@'.$package.'::ISA';
392              
393 0 0 0       unless( %plan && $plan{$phase} ) {
394 0 0         $self->_buildPhase( $phase, @isa ) if @isa;
395 0           next;
396             }
397              
398 0           foreach my $method ( @{$plan{$phase}} ) {
  0            
399              
400 0 0 0       if( not ref $method and $method eq 'SUPER' ) {
401 0 0         $self->_buildPhase( $phase, @isa ) if @isa;
402 0           next;
403             }
404              
405 0           push @{$self->{_plan}{$phase}{MIDDLE}}, $method;
  0            
406             }
407             }
408             }
409              
410             #========
411             1;
412              
413             =head2 Acknowledgements
414              
415             I would like to thank the members of the CGI::Application mailing list that have
416             participated in the discussions that resulted in this module, particularly
417             Rob Kinyon, Cees Hek, Mark Stosberg, Michael Peters and David Naughton. And of
418             course to Jesse Erlbaum for introducing me to sane methods of web development
419             with CGI::Application.
420              
421             =head2 Authors
422              
423             Stephen Howard
424              
425             =head2 License
426              
427             This module may be distributed under the same terms as Perl itself.
428              
429             =cut
430