File Coverage

blib/lib/WebDyne/Chain.pm
Criterion Covered Total %
statement 25 161 15.5
branch 0 44 0.0
condition 0 8 0.0
subroutine 9 15 60.0
pod 0 2 0.0
total 34 230 14.7


line stmt bran cond sub pod time code
1             #
2             #
3             # Copyright (C) 2006-2010 Andrew Speer .
4             # All rights reserved.
5             #
6             # This file is part of WebDyne::Chain.
7             #
8             # WebDyne::Chain is free software; you can redistribute it and/or modify
9             # it under the terms of the GNU General Public License as published by
10             # the Free Software Foundation; either version 2 of the License, or
11             # (at your option) any later version.
12             #
13             # This program is distributed in the hope that it will be useful,
14             # but WITHOUT ANY WARRANTY; without even the implied warranty of
15             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16             # GNU General Public License for more details.
17             #
18             # You should have received a copy of the GNU General Public License
19             # along with this program; if not, write to the Free Software
20             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21             #
22             #
23             package WebDyne::Chain;
24              
25              
26             # Compiler Pragma
27             #
28 1     1   21919 sub BEGIN { $^W=0 };
29 1     1   7 use strict qw(vars);
  1         2  
  1         27  
30 1     1   4 use vars qw($VERSION);
  1         1  
  1         46  
31 1     1   9 use warnings;
  1         2  
  1         38  
32 1     1   4 no warnings qw(uninitialized);
  1         1  
  1         26  
33              
34              
35             # Webmod, WebDyne Modules.
36             #
37 1     1   1336 use WebDyne;
  1         234229  
  1         45  
38 1     1   10 use WebDyne::Constant;
  1         2  
  1         577  
39 1     1   872 use WebDyne::Chain::Constant;
  1         4  
  1         49  
40 1     1   8 use WebDyne::Base;
  1         2  
  1         72  
41              
42              
43             # Version information in a formate suitable for CPAN etc. Must be
44             # all on one line
45             #
46             $VERSION='1.050';
47              
48              
49             # Debug using WebDyne debug handler
50             #
51             0 && debug("%s loaded, version $VERSION", __PACKAGE__);
52              
53              
54             # Shortcut error handler, save using ISA;
55             #
56             require WebDyne::Err;
57             *err_html=\&WebDyne::Err::err_html || *err_html;
58              
59              
60             # Package wide hash ref for data storage
61             #
62             my %Package;
63              
64              
65             # Make all errors non-fatal
66             #
67             errnofatal(1);
68              
69              
70             # And done
71             #
72             1;
73              
74              
75             #------------------------------------------------------------------------------
76              
77              
78             sub handler : method {
79              
80              
81             # Get class, request object
82             #
83 0     0 0   my ($self, $r, $param_hr)=@_;
84 0   0       my $class=ref($self) || do {
85              
86              
87             # Need new self ref
88             #
89             my %self=(
90              
91             _time => time(),
92             _r => $r,
93             %{delete $self->{'_self'}},
94              
95             );
96             $self=bless \%self, $self;
97             ref($self);
98              
99              
100             };
101              
102              
103             # Setup error handlers
104             #
105 0     0     local $SIG{__DIE__} =sub { return $self->err_html(@_) };
  0            
106 0 0   0     local $SIG{__WARN__}=sub { return $self->err_html(@_) } if $WEBDYNE_WARNINGS_FATAL;
  0            
107              
108              
109             # Debug
110             #
111 0           0 && debug("in WebDyne::Chain::handler, class $class, r $r, self $self, param_hr %s",
112             Dumper($param_hr));
113              
114              
115             # Log URI
116             #
117 0           0 && debug("URI %s", $r->uri());
118              
119              
120             # Get string of modules to chain
121             #
122 0           my @module;
123 0 0         if (my $module_ar=$param_hr->{'meta'}{'webdynechain'}) {
    0          
124 0           0 && debug("using module_ar $module_ar %s from meta", Dumper($module_ar));
125 0           @module=@{$module_ar};
  0            
126             }
127             elsif (my $module=$r->dir_config('WebDyneChain')) {
128 0           0 && debug("using module $module dir_config");
129 0           @module=split(/\s+/, $module);
130             }
131             else {
132 0           0 && debug('could not find any module chain info');
133             }
134              
135              
136             # WebDyne::Chain must be the first handler in line, Webdyne the last
137             #
138 0 0         unshift @module, __PACKAGE__ unless ($module[0] eq +__PACKAGE__);
139 0 0         push @module, 'WebDyne' unless ($module[$#module] eq 'WebDyne');
140 0           0 && debug('final module chain %s', join('*', @module));
141              
142              
143             # Store current chain
144             #
145 0           $Package{'_chain_ar'}=\@module;
146              
147              
148             # If only two modules (WebDyne::Chain, WebDyne) something is wrong
149             #
150 0 0         if (@module==2) {
151             return
152 0           $self->err_html('unable to determine module chain - have you set WebDyneChain var ?');
153             }
154              
155              
156             # Get location. Used to use r->location, now use module array to generate pseudo
157             # location data;
158             #
159 0           my $location=join(undef, @module);
160 0           0 && debug("location $location");
161 0 0         unless ($Package{'_chain_loaded_hr'}{$location}++) {
162 0           0 && debug("modules not loaded, doing now");
163 0           local $SIG{'__DIE__'};
164 0           foreach my $package (@module) {
165 0 0         eval("require $package") ||
166             return $self->err_html("unable to load package $package, ".lcfirst($@));
167 0           0 && debug("loaded $package");
168             }
169             }
170              
171              
172             # If location not same as last time we were run, then unload chain
173             #
174 0 0         if ((my $location_current=$Package{'_location_current'}) ne $location) {
175              
176              
177             # Need to unload cached code refs
178             #
179 0           0 && debug("location_current '$location_current' is ne this location ('$location'). restoring cr's");
180 0           &ISA_restore();
181              
182              
183             # Update location
184             #
185 0           $Package{'_location_current'}=$location;
186              
187              
188             # If code ref's cached, load up now
189             #
190 0 0         if (my $chain_hr=$Package{'_chain_hr'}{$location}) {
191              
192              
193             # Debug
194             #
195 0           0 && debug("found cached code ref's for location $location loading");
196              
197              
198             # Yes found, load up
199             #
200 0           while (my($method,$cr)=each %{$chain_hr}) {
  0            
201              
202              
203             # Debug
204             #
205 0           0 && debug("loading cr $cr for method $method");
206              
207              
208             # Install code ref
209             #
210 0           *{$method}=$cr;
  0            
211              
212             }
213              
214              
215             # Update current pointer
216             #
217 0           $Package{'_chain_current_hr'}=$chain_hr;
218              
219              
220             }
221             }
222             else {
223              
224 0           0 && debug('location chain same as last request, caching');
225              
226             }
227              
228              
229             # Debug
230             #
231 0           0 && debug('module array %s', Dumper(\@module));
232              
233              
234             # All done, pass onto next handler in chain. NOTE no error handler (eg || $self->err_html). It is
235             # not our job to check for errors here, we should just pass back whatever the next handler does.
236             #
237 0           return $self->SUPER::handler($r, @_[2..$#_]);
238              
239              
240             # Only get here if error handler invoked
241             #
242 0           RENDER_ERROR:
243             return $self->err_html();
244              
245              
246             # Only get here if subrequest invoked.
247 0           HANDLER_COMPLETE:
248             return &Apache::OK;
249              
250              
251             }
252              
253              
254             sub ISA_restore {
255              
256              
257             # Get cuurent chain hash
258             #
259 0     0 0   my $chain_hr=delete $Package{'_chain_current_hr'};
260 0           0 && debug('in ISA_restore, chain %s', Dumper($chain_hr));
261              
262              
263             # Go through each module, restoring
264             #
265 0           foreach my $method (keys %{$chain_hr}) {
  0            
266              
267              
268             # Free up
269             #
270 0           0 && debug("free $method");
271 0           undef *{$method};
  0            
272              
273              
274             }
275              
276              
277             }
278              
279              
280             sub DESTROY {
281              
282              
283             # Get chain array ref
284             #
285 0     0     my $self=shift();
286 0           my $chain_ar=$Package{'_chain_ar'};
287 0           0 && debug("self $self, going through DESTROY chain %s", Dumper($chain_ar));
288              
289              
290             # Handle destroys specially, mini version of AUTOLOAD code below
291             #
292 0           foreach my $i (1 .. $#{$chain_ar}) {
  0            
293 0           my $package_chain=$chain_ar->[$i];
294 0           0 && debug("looking for DESTROY $package_chain");
295 0 0         if (my $cr=UNIVERSAL::can($package_chain, 'DESTROY')) {
296 0           0 && debug("DESTROY hit on $package_chain");
297 0           $cr->($self);
298             }
299             }
300              
301              
302             # Destroy object
303             #
304 0           %{$self}=();
  0            
305 0           undef $self;
306              
307              
308             }
309              
310              
311              
312             sub UNIVERSAL::AUTOLOAD {
313              
314              
315             # Get self ref, calling class, autoloaded method
316             #
317 0     0     my $self=$_[0];
318 0   0       my $autoload=$UNIVERSAL::AUTOLOAD || return;
319              
320              
321             # Do not handle DESTROY's
322             #
323 0 0         return if $autoload=~/::DESTROY$/;
324              
325              
326             # Debug
327             #
328 0           0 && debug("in UNIVERSAL::AUTOLOAD, self $self, autoload $autoload, caller %s",
329             Dumper([(caller(1))[0..3]]));
330              
331              
332             # Get apache request ref, location. If not present means called by non-WebDyne class, not supported
333             #
334 0           my $r; {
335 0           local $SIG{'__DIE__'}=undef;
  0            
336 0 0         unless (eval{ ref($self) && ($r=$self->{'_r'}) }) {
  0 0          
337 0           err("call to run %s UNIVERSAL::AUTOLOAD for non chained method '$autoload', self ref '$self'.", +__PACKAGE__);
338 0           goto RENDER_ERROR;
339             }
340             }
341              
342              
343              
344             # Get method user was looking for, keep full package name.
345             #
346 0           my ($package_autoload, $method_autoload)=($autoload=~/(.*)::(.*?)$/);
347 0           0 && debug("package_autoload $package_autoload, method_autoload $method_autoload");
348              
349              
350             # And chain for this location
351             #
352 0           my $chain_ar=$Package{'_chain_ar'};
353 0           my $location=join(undef, @{$chain_ar});
  0            
354 0           0 && debug('going through chain %s', Dumper($chain_ar));
355              
356              
357             # Caller information
358             #
359 0           my $subroutine_caller=(caller(1))[3];
360 0           my $subroutine_caller_cr=\&{"$subroutine_caller"};
  0            
361 0           my ($package_caller, $method_caller)=($subroutine_caller=~/(.*)::(.*?)$/);
362 0           0 && debug("package_caller $package_caller, method_caller $method_caller");
363              
364              
365             # If SUPER method trawl through chain to find the package it was called from, make sure we start
366             # from there in iteration code below
367             #
368 0           my $i=0;
369 0 0         if ($autoload=~/\QSUPER::$method_autoload\E$/) {
370 0           0 && debug("SUPER method");
371 0           for (1; $i < @{$chain_ar}; $i++) {
  0            
372 0 0         if (UNIVERSAL::can($chain_ar->[$i], $method_caller) eq $subroutine_caller_cr) {
373 0           $i++;
374 0           last;
375             }
376             else {
377 0           0 && debug("miss on package $chain_ar->[$i], $_ ne $subroutine_caller_cr");
378             }
379             }
380 0           0 && debug("loop finished, i $i, chain_ar %s", $#{$chain_ar});
381             }
382              
383              
384             # Iterate through the chain (in order) looking for the method
385             #
386 0           foreach $i ($i .. $#{$chain_ar}) {
  0            
387              
388              
389             # Can this package in the chain support the calling method ?
390             #
391 0           0 && debug("look for $method_autoload in package $chain_ar->[$i]");
392 0 0         if (my $cr=UNIVERSAL::can($chain_ar->[$i], $method_autoload)) {
393              
394              
395             # Yes. Check for loops
396             #
397 0 0         if ($cr eq $subroutine_caller_cr) {
398 0           err("detected AUTOLOAD loop for method '$method_autoload' ".
399 0           "package $package_caller. Current chain: %s", join(', ', @{$chain_ar}));
400 0           goto RENDER_ERROR;
401             }
402              
403              
404             # Update
405             #
406 0           0 && debug('hit');
407 0           *{$autoload}=$cr;
  0            
408              
409              
410             # And keep a record
411             #
412 0           $Package{'_chain_hr'}{$location}{$autoload}=$cr;
413 0   0       $Package{'_chain_current_hr'} ||= $Package{'_chain_hr'}{$location};
414              
415              
416             # And dispatch. The commented out code is good for debugging internal
417             # server errors, esp if comment out *{$autoload} above and turn on
418             # debugging
419             #
420 0           goto &{$cr};
  0            
421              
422             }
423             else {
424              
425              
426             # Debug
427             #
428 0           0 && debug("unable to find method $method_autoload in package $chain_ar->[$i]");
429              
430             }
431              
432             }
433              
434              
435             # Last resort - look back through call chain
436             #
437 0           0 && debug("checking back through callstack for method $method_autoload");
438 0           my %chain=map { $_=> 1} @{$chain_ar};
  0            
  0            
439 0           my @caller;
440 0           for ($i=0; my $caller=(caller($i))[0]; $i++) {
441 0 0         next if $chain{$caller}++; #already looked there
442 0           push @caller, $caller;
443 0 0         if (my $cr=UNIVERSAL::can($caller, $method_autoload)) {
444 0 0         if ($cr eq $subroutine_caller_cr) {
445 0           err("detected AUTOLOAD loop for method '$method_autoload' ".
446 0           "package $package_caller. Current chain: %s", join(', ', @{$chain_ar}));
447 0           goto RENDER_ERROR;
448             }
449 0 0         if ($WEBDYNE_AUTOLOAD_POLLUTE) {
450 0           *{$autoload}=$cr;
  0            
451 0           $Package{'_chain_hr'}{$location}{$autoload}=$cr;
452             }
453 0           goto &{$cr}
  0            
454             }
455             }
456              
457              
458             # Return err
459             #
460 0           err("method '$method_autoload' not found in call chain: %s", join(',', @caller));
461 0           goto RENDER_ERROR;
462              
463             }
464              
465             __END__