File Coverage

blib/lib/Apache2/Controller/Dispatch/HashTree.pm
Criterion Covered Total %
statement 18 18 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 24 24 100.0


line stmt bran cond sub pod time code
1             package Apache2::Controller::Dispatch::HashTree;
2              
3             =head1 NAME
4              
5             Apache2::Controller::Dispatch::HashTree -
6             Hash tree dispatch for L
7              
8             =head1 VERSION
9              
10             Version 1.001.001
11              
12             =cut
13              
14 2     2   3513 use version;
  2         2246  
  2         13  
15             our $VERSION = version->new('1.001.001');
16              
17             =head1 SYNOPSIS
18              
19            
20             SetHandler modperl
21             PerlInitHandler MyApp::Dispatch
22            
23              
24             # lib/MyApp::Dispatch:
25              
26             package MyApp::Dispatch;
27             use base qw(
28             Apache2::Controller::Dispatch::HashTree
29             );
30              
31             # return a hash reference from dispach_map()
32             sub dispatch_map { return {
33             foo => {
34             default => 'MyApp::C::Foo',
35             bar => {
36             biz => 'MyApp::C::Biz',
37             baz => 'MyApp::C::Baz',
38             },
39             },
40             default => 'MyApp::C::Default',
41             } }
42              
43             1;
44             __END__
45              
46             This maps uri's to controller modules as follows:
47              
48             /subdir/foo MyApp::C::Foo->default()
49              
50             /subdir/foo/bar MyApp::C::Foo->bar()
51              
52             /subdir/foo/bar/zerm MyApp::C::Foo->bar(), path_args == ['zerm']
53              
54             /subdir/foo/bar/biz MyApp::C::Biz->default()
55              
56             /subdir/foo/biz/baz/noz/wiz MyApp::C::Baz->noz(), path_args == ['wiz']
57              
58             In the second example, if C<> did not implement or allow
59             C<> as a controller method, then this would select
60             C<default()>>.
61              
62             =head1 DESCRIPTION
63              
64             Implements find_controller() for Apache2::Controller::Dispatch with
65             a simple hash-based mapping. Uses substr to divide the uri and
66             exists to check cached mappings, so it should be pretty fast.
67              
68             This dispatches URI's in a case-insensitive fashion.
69              
70             =head1 METHODS
71              
72             =cut
73              
74 2     2   191 use strict;
  2         4  
  2         63  
75 2     2   11 use warnings FATAL => 'all';
  2         4  
  2         77  
76 2     2   814 use English '-no_match_vars';
  2         4316  
  2         14  
77 2     2   1137 use Carp qw( confess );
  2         4  
  2         133  
78              
79 2     2   13 use base qw( Apache2::Controller::Dispatch );
  2         3  
  2         861  
80              
81             use Apache2::Controller::X;
82             use Apache2::Controller::Funk qw( controller_allows_method check_allowed_method );
83              
84             use Log::Log4perl qw(:easy);
85             use YAML::Syck;
86              
87             =head2 find_controller
88              
89             Find the controller and method for a given URI from the data
90             set in the dispatch class module.
91              
92             =cut
93              
94             sub find_controller {
95             my ($self) = @_;
96             my $dispatch_map = $self->get_dispatch_map();
97             my $r = $self->{r};
98             my $location = $r->location();
99             my $uri = $r->uri();
100             my $uri_below_loc = substr $uri, length $location;
101              
102             DEBUG(sub{Dump({
103             uri => $uri,
104             uri_below_loc => $uri_below_loc,
105             location => $location,
106             })});
107              
108             # efficiently split up the uri into an array of path parts
109             my @path;
110             my $j = 0;
111             my $uri_len = length $uri_below_loc;
112             my $last_char_idx = $uri_len - 1;
113             my $prev_char = q{};
114             my $uri_without_leading_slash = '';
115             CHAR:
116             for (my $i = 0; $i <= $last_char_idx; $i++) {
117             my $char = substr $uri_below_loc, $i, 1;
118             DEBUG(sub { "j=$j; char $i = '$char' (".ord($char).")" });
119             if ($char eq '/') {
120             # skip over first /
121             if ($i == 0) {
122             $prev_char = $char;
123             next CHAR;
124             }
125              
126             # skip over repeat //'s
127             next CHAR if $char eq $prev_char;
128              
129             # skip a trailing /
130             last CHAR if $i == $last_char_idx;
131              
132             # not skipped, so iterate the path counter
133             $j++;
134             }
135             else {
136             $path[$j] .= $char;
137             DEBUG("added $char to path[$j] ($path[$j])");
138             }
139             $prev_char = $char;
140             $uri_without_leading_slash .= $char;
141             }
142             $uri_below_loc = $uri_without_leading_slash;
143             DEBUG("uri_below_loc is now $uri_below_loc");
144              
145             # follow these keys through the hash and push remaining path parts
146             # to an array for after we're done searching for the method
147             my $node = $dispatch_map;
148              
149             DEBUG(sub{"path: (@path)"});
150              
151             my @trace_path;
152             @trace_path = map {
153             ref $node # wow, i didn't know you could do this...
154             ? do { $node = $node->{$_}; $node }
155             : undef
156             } @path;
157             DEBUG(sub{"dispatch hash trace_path:\n".Dump(\@trace_path)});
158            
159             my %results = ();
160             my @path_args;
161              
162             FIND_NODE:
163             for (my $i = $#trace_path; $i >= 0; $i--) {
164              
165             next FIND_NODE if !exists $trace_path[$i];
166              
167             my $node = $trace_path[$i];
168              
169             my $part = $path[$i];
170              
171             DEBUG(sub { "part = '$part', i = $i, path=(@path), node = ".Dump($node) });
172              
173             my $ref = ref $node;
174              
175             my $maybe_method = $path[$i + 1];
176             my $maybe_controller = $ref ? $node->{default} : $node;
177              
178             next FIND_NODE if !$maybe_controller; # no default specified, no matches
179              
180             DEBUG(sub {
181             "ctrl? => '$maybe_controller', method? => ".($maybe_method || '[none]')
182             });
183              
184             if ( $maybe_method
185             && controller_allows_method($maybe_controller => $maybe_method)
186             ) {
187             # got it!
188             $results{controller} = $maybe_controller;
189             $results{method} = $maybe_method;
190             $results{relative_uri} = join('/', @path[ 0 .. $i ]);
191             @path_args = @path[ $i + 2 .. $#path ];
192             last FIND_NODE;
193             }
194             else { # maybe 'default' here?
195             if (controller_allows_method($maybe_controller => 'default')) {
196             $results{controller} = $maybe_controller;
197             $results{method} = 'default';
198             $results{relative_uri} = join('/', @path[ 0 .. $i ]);
199             @path_args = @path[ $i + 1 .. $#path ];
200             last FIND_NODE;
201             }
202             else {
203             # not here... go back one
204             next FIND_NODE;
205             }
206             }
207             }
208              
209             # if still no controller, select the default
210             if (!$results{controller}) {
211             my $ctrl = $dispatch_map->{default};
212              
213             a2cx "$uri no default controller" if !$ctrl;
214              
215             a2cx "$uri no references allowed in dispatch_map for default"
216             if ref $ctrl;
217              
218             $results{controller} = $ctrl;
219              
220             # and find a method.
221             my $maybe_method = $path[0];
222             if ( $maybe_method
223             && controller_allows_method($results{controller}, $maybe_method)
224             ) {
225             $results{method} = $maybe_method;
226             @path_args = @path[ 1 .. $#path ] if exists $path[1];
227             }
228             elsif (controller_allows_method($results{controller}, 'default')) {
229             $results{method} = 'default';
230             @path_args = @path[ 0 .. $#path ] if exists $path[0];
231             }
232             else {
233             a2cx "$uri cannot find a working method in $results{controller}";
234             }
235              
236             # relative uri is ''
237             $results{relative_uri} = '';
238             }
239              
240             DEBUG(sub{Dump({
241             path_args => \@path_args,
242             results => \%results,
243             })});
244              
245             # make sure this worked
246             a2cx "did not detect $_"
247             for grep !exists $results{$_},
248             qw( controller method relative_uri );
249              
250             # save the info in pnotes
251             my $pnotes = $r->pnotes;
252             $pnotes->{a2c}{$_} = $results{$_} for keys %results;
253             $pnotes->{a2c}{path_args} = \@path_args;
254              
255             # now try finding a matching module in dispatch_map
256              
257             #######################################################
258             return $results{controller};
259             }
260              
261             =head1 SEE ALSO
262              
263             L
264              
265             L
266              
267             L
268              
269             =head1 AUTHOR
270              
271             Mark Hedges, C
272              
273             =head1 COPYRIGHT AND LICENSE
274              
275             Copyright 2008-2010 Mark Hedges. CPAN: markle
276              
277             This library is free software; you can redistribute it and/or modify
278             it under the same terms as Perl itself.
279              
280             This software is provided as-is, with no warranty
281             and no guarantee of fitness
282             for any particular purpose.
283              
284             =cut
285              
286              
287             1;