Added a benchmark to the auto test, CAT_BENCHMARK=1
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
3use strict;
fbcc39ad 4use base 'Class::Accessor::Fast';
a2f2cde9 5use Catalyst::Exception;
f05af9ba 6use Catalyst::Utils;
fbcc39ad 7use Catalyst::Action;
b7aebc12 8use Catalyst::ActionContainer;
1abd6db7 9use Text::ASCIITable;
1abd6db7 10use Tree::Simple;
11use Tree::Simple::Visitor::FindByPath;
12
fbcc39ad 13# Stringify to class
14use overload '""' => sub { return ref shift }, fallback => 1;
15
16__PACKAGE__->mk_accessors(qw/actions tree/);
1abd6db7 17
1abd6db7 18=head1 NAME
19
9c053379 20Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 21
22=head1 SYNOPSIS
23
24See L<Catalyst>.
25
26=head1 DESCRIPTION
27
28=head1 METHODS
29
30=over 4
31
fbcc39ad 32=item $self->detach( $c, $command [, \@arguments ] )
6ef62eb2 33
34=cut
35
36sub detach {
fbcc39ad 37 my ( $self, $c, $command, @args ) = @_;
bd7d2e94 38 $c->forward( $command, @args ) if $command;
fbcc39ad 39 die $Catalyst::DETACH;
6ef62eb2 40}
41
fbcc39ad 42=item $self->dispatch($c)
1abd6db7 43
44=cut
45
46sub dispatch {
fbcc39ad 47 my ( $self, $c ) = @_;
cfd04b0c 48
66e28e3f 49 if ( $c->action ) {
50
51 my @containers = $self->get_containers( $c->namespace );
52 my %actions;
53 foreach my $name (qw/begin auto end/) {
54 $actions{$name} = [
55 map { $_->{$name} }
56 grep { exists $_->{$name} }
57 map { $_->actions }
58 @containers
59 ];
60 }
1abd6db7 61
fbcc39ad 62 # Errors break the normal flow and the end action is instantly run
63 my $error = 0;
64
1abd6db7 65 # Execute last begin
66 $c->state(1);
66e28e3f 67 if ( my $begin = @{ $actions{begin} }[-1] ) {
cfd04b0c 68 $begin->execute($c);
fbcc39ad 69 $error++ if scalar @{ $c->error };
1abd6db7 70 }
71
72 # Execute the auto chain
1196a46d 73 my $autorun = 0;
cfd04b0c 74 for my $auto ( @{ $actions{auto} } ) {
fbcc39ad 75 last if $error;
9ddd9d05 76 $autorun++;
cfd04b0c 77 $auto->execute($c);
fbcc39ad 78 $error++ if scalar @{ $c->error };
1abd6db7 79 last unless $c->state;
80 }
81
82 # Execute the action or last default
e5f21aa2 83 my $mkay = $autorun ? $c->state ? 1 : 0 : 1;
61cfdd57 84 if ( ( my $action = $c->req->action ) && $mkay ) {
fbcc39ad 85 unless ($error) {
66e28e3f 86 $c->action->execute($c);
87 $error++ if scalar @{ $c->error };
1abd6db7 88 }
89 }
90
91 # Execute last end
cfd04b0c 92 if ( my $end = @{ $actions{end} }[-1] ) {
93 $end->execute($c);
1abd6db7 94 }
fbcc39ad 95 }
96
97 else {
1abd6db7 98 my $path = $c->req->path;
99 my $error = $path
100 ? qq/Unknown resource "$path"/
101 : "No default action defined";
102 $c->log->error($error) if $c->debug;
103 $c->error($error);
104 }
105}
106
fbcc39ad 107=item $self->forward( $c, $command [, \@arguments ] )
1abd6db7 108
109=cut
110
111sub forward {
fbcc39ad 112 my $self = shift;
1abd6db7 113 my $c = shift;
114 my $command = shift;
99fe1710 115
1abd6db7 116 unless ($command) {
117 $c->log->debug('Nothing to forward to') if $c->debug;
118 return 0;
119 }
99fe1710 120
71c3bcc3 121 # Relative forwards from detach
fbcc39ad 122 my $caller = ( caller(1) )[0]->isa('Catalyst::Dispatcher')
123 && ( ( caller(2) )[3] =~ /::detach$/ ) ? caller(3) : caller(1);
71c3bcc3 124
e0fc6749 125 my $arguments = ( ref( $_[-1] ) eq 'ARRAY' ) ? pop(@_) : $c->req->args;
99fe1710 126
fbcc39ad 127 my $results = [];
128
8199eac3 129 my $command_copy = $command;
130
131 unless ( $command_copy =~ s/^\/// ) {
5d68b17b 132 my $namespace =
133 Catalyst::Utils::class2prefix( $caller, $c->config->{case_sensitive} ) || '';
134 $command_copy = "${namespace}/${command}";
1abd6db7 135 }
99fe1710 136
8199eac3 137 unless ( $command_copy =~ /\// ) {
138 $results = $c->get_action( $command_copy, '/' );
139 }
e494bd6b 140 else {
8199eac3 141 my @extra_args;
142 DESCEND: while ( $command_copy =~ s/^(.*)\/(\w+)$/$1/ ) {
143 my $tail = $2;
144 $results = $c->get_action( $tail, $1 );
145 if ( @{$results} ) {
146 $command = $tail;
147 push( @{$arguments}, @extra_args );
148 last DESCEND;
149 }
150 unshift( @extra_args, $tail );
151 }
e494bd6b 152 }
99fe1710 153
1abd6db7 154 unless ( @{$results} ) {
bd7d2e94 155
fbcc39ad 156 unless ( $c->components->{$command} ) {
bd7d2e94 157 my $error =
158qq/Couldn't forward to command "$command". Invalid action or component./;
3b2ed580 159 $c->error($error);
160 $c->log->debug($error) if $c->debug;
1abd6db7 161 return 0;
162 }
bd7d2e94 163
1f6bb799 164 my $class = $command;
1abd6db7 165 my $method = shift || 'process';
99fe1710 166
1f6bb799 167 if ( my $code = $c->components->{$class}->can($method) ) {
fbcc39ad 168 my $action = Catalyst::Action->new(
169 {
170 code => $code,
171 reverse => "$class->$method",
172 namespace => $class,
cfd04b0c 173 prefix => $class,
fbcc39ad 174 }
175 );
176 $results = [ [$action] ];
177 }
178
179 else {
bd7d2e94 180 my $error =
181 qq/Couldn't forward to "$class". Does not implement "$method"/;
3b2ed580 182 $c->error($error);
183 $c->log->debug($error)
1abd6db7 184 if $c->debug;
185 return 0;
186 }
99fe1710 187
1abd6db7 188 }
bd7d2e94 189
190 local $c->request->{arguments} = [ @{$arguments} ];
99fe1710 191
1abd6db7 192 for my $result ( @{$results} ) {
fbcc39ad 193 $result->[0]->execute($c);
e0fc6749 194 return if scalar @{ $c->error };
1abd6db7 195 last unless $c->state;
196 }
99fe1710 197
1abd6db7 198 return $c->state;
199}
200
fbcc39ad 201=item $self->prepare_action($c)
202
203=cut
204
205sub prepare_action {
206 my ( $self, $c ) = @_;
207 my $path = $c->req->path;
208 my @path = split /\//, $c->req->path;
209 $c->req->args( \my @args );
210
211 while (@path) {
212 $path = join '/', @path;
213 if ( my $result = ${ $c->get_action($path) }[0] ) {
214
215 # It's a regex
216 if ($#$result) {
217 my $match = $result->[1];
218 my @snippets = @{ $result->[2] };
219 $c->log->debug(
220 qq/Requested action is "$path" and matched "$match"/)
221 if $c->debug;
222 $c->log->debug(
223 'Snippets are "' . join( ' ', @snippets ) . '"' )
224 if ( $c->debug && @snippets );
225 $c->req->action($match);
226 $c->req->snippets( \@snippets );
227 }
228
229 else {
230 $c->req->action($path);
231 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
232 }
1abd6db7 233
fbcc39ad 234 $c->req->match($path);
66e28e3f 235 $c->action($result->[0]);
236 $c->namespace($result->[0]->prefix);
fbcc39ad 237 last;
238 }
239 unshift @args, pop @path;
240 }
241
242 unless ( $c->req->action ) {
66e28e3f 243 my $result = @{$c->get_action('default', $c->req->path, 1) || []}[-1];
244 if ($result) {
245 $c->action( $result->[0] );
246 $c->namespace( $c->req->path );
247 $c->req->action('default');
248 $c->req->match('');
249 }
fbcc39ad 250 }
251
252 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
253 if ( $c->debug && @args );
254}
255
256=item $self->get_action( $c, $action, $namespace, $inherit )
1abd6db7 257
258=cut
259
260sub get_action {
fbcc39ad 261 my ( $self, $c, $action, $namespace, $inherit ) = @_;
1abd6db7 262 return [] unless $action;
263 $namespace ||= '';
2d16b61a 264 $inherit ||= 0;
99fe1710 265
1abd6db7 266 if ($namespace) {
c8d9780f 267
cfd04b0c 268 my @match = $self->get_containers( $namespace );
99fe1710 269
c8d9780f 270 my @results;
99fe1710 271
cfd04b0c 272 foreach my $child ($inherit ? @match: $match[-1]) {
273 my $node = $child->actions;
c8d9780f 274 push(@results, [ $node->{$action} ]) if defined $node->{$action};
1abd6db7 275 }
276 return \@results;
277 }
99fe1710 278
fbcc39ad 279 elsif ( my $p = $self->actions->{plain}->{$action} ) { return [ [$p] ] }
280 elsif ( my $r = $self->actions->{regex}->{$action} ) { return [ [$r] ] }
99fe1710 281
1abd6db7 282 else {
99fe1710 283
fbcc39ad 284 for my $i ( 0 .. $#{ $self->actions->{compiled} } ) {
285 my $name = $self->actions->{compiled}->[$i]->[0];
286 my $regex = $self->actions->{compiled}->[$i]->[1];
99fe1710 287
2d752b2a 288 if ( my @snippets = ( $action =~ $regex ) ) {
fbcc39ad 289 return [
290 [ $self->actions->{regex}->{$name}, $name, \@snippets ] ];
1abd6db7 291 }
99fe1710 292
1abd6db7 293 }
294 }
295 return [];
296}
297
cfd04b0c 298=item $self->get_containers( $namespace )
299
300=cut
301
302sub get_containers {
303 my ( $self, $namespace ) = @_;
304
305 return ($self->tree->getNodeValue) if $namespace eq '/';
306
307 my $visitor = Tree::Simple::Visitor::FindByPath->new;
308 my @path = split('/', $namespace);
309 $visitor->setSearchPath( @path );
310 $self->tree->accept($visitor);
311
312 my @match = $visitor->getResults;
313 @match = ($self->tree) unless @match;
314
315 if (!defined $visitor->getResult) {
316 my $extra = $path[(scalar @match) - 1];
317 last unless $extra;
318 $visitor->setSearchPath($extra);
319 $match[-1]->accept($visitor);
320 push(@match, $visitor->getResult) if defined $visitor->getResult;
321 }
322
323 return map { $_->getNodeValue } @match;
324}
325
fbcc39ad 326=item $self->set_action( $c, $action, $code, $namespace, $attrs )
1abd6db7 327
328=cut
329
330sub set_action {
fbcc39ad 331 my ( $self, $c, $method, $code, $namespace, $attrs ) = @_;
1abd6db7 332
e494bd6b 333 my $prefix =
334 Catalyst::Utils::class2prefix( $namespace, $c->config->{case_sensitive} )
335 || '';
1abd6db7 336 my %flags;
337
338 for my $attr ( @{$attrs} ) {
0299ba22 339 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
340 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
749472d6 341 elsif ( $attr =~ /^Path\(\s*(.+)\s*\)$/i ) {
342 push @{ $flags{path} }, $1;
343 }
344 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
0299ba22 345 elsif ( $attr =~ /^(Regex|Regexp)\(\s*(.+)\s*\)$/i ) {
749472d6 346 push @{ $flags{regex} }, $2;
0299ba22 347 }
1abd6db7 348 }
349
d0e524d2 350 if ( $flags{private} && ( keys %flags > 1 ) ) {
8d4e224b 351 $c->log->debug( 'Bad action definition "'
d0e524d2 352 . join( ' ', @{$attrs} )
8d4e224b 353 . qq/" for "$namespace->$method"/ )
354 if $c->debug;
d0e524d2 355 return;
356 }
1abd6db7 357 return unless keys %flags;
358
fbcc39ad 359 my $parent = $self->tree;
1abd6db7 360 my $visitor = Tree::Simple::Visitor::FindByPath->new;
99fe1710 361
b7aebc12 362 if ($prefix) {
363 for my $part ( split '/', $prefix ) {
1abd6db7 364 $visitor->setSearchPath($part);
365 $parent->accept($visitor);
b7aebc12 366 my $child = $visitor->getResult;
367
368 unless ($child) {
369 my $container = Catalyst::ActionContainer->new(
370 { part => $part, actions => {} });
371 $child = $parent->addChild( Tree::Simple->new($container) );
372 $visitor->setSearchPath($part);
373 $parent->accept($visitor);
374 $child = $visitor->getResult;
375 }
376
377 $parent = $child;
1abd6db7 378 }
1abd6db7 379 }
99fe1710 380
fbcc39ad 381 my $reverse = $prefix ? "$prefix/$method" : $method;
382
383 my $action = Catalyst::Action->new(
384 {
385 code => $code,
386 reverse => $reverse,
387 namespace => $namespace,
cfd04b0c 388 prefix => $prefix,
fbcc39ad 389 }
390 );
391
b7aebc12 392 $parent->getNodeValue->actions->{$method} = $action;
fbcc39ad 393
749472d6 394 my @path;
395 for my $path ( @{ $flags{path} } ) {
396 $path =~ s/^\w+//;
397 $path =~ s/\w+$//;
398 if ( $path =~ /^\s*'(.*)'\s*$/ ) { $path = $1 }
399 if ( $path =~ /^\s*"(.*)"\s*$/ ) { $path = $1 }
400 push @path, $path;
1abd6db7 401 }
749472d6 402 $flags{path} = \@path;
403
404 my @regex;
405 for my $regex ( @{ $flags{regex} } ) {
406 $regex =~ s/^\w+//;
407 $regex =~ s/\w+$//;
408 if ( $regex =~ /^\s*'(.*)'\s*$/ ) { $regex = $1 }
409 if ( $regex =~ /^\s*"(.*)"\s*$/ ) { $regex = $1 }
410 push @regex, $regex;
1abd6db7 411 }
749472d6 412 $flags{regex} = \@regex;
1abd6db7 413
749472d6 414 if ( $flags{local} || $flags{global} ) {
415 push( @{ $flags{path} }, $prefix ? "/$prefix/$method" : "/$method" )
416 if $flags{local};
99fe1710 417
749472d6 418 push( @{ $flags{path} }, "/$method" ) if $flags{global};
419 }
99fe1710 420
749472d6 421 for my $path ( @{ $flags{path} } ) {
422 if ( $path =~ /^\// ) { $path =~ s/^\/// }
423 else { $path = $prefix ? "$prefix/$path" : $path }
424 $self->actions->{plain}->{$path} = $action;
1abd6db7 425 }
99fe1710 426
749472d6 427 for my $regex ( @{ $flags{regex} } ) {
fbcc39ad 428 push @{ $self->actions->{compiled} }, [ $regex, qr#$regex# ];
429 $self->actions->{regex}->{$regex} = $action;
1abd6db7 430 }
1abd6db7 431}
432
fbcc39ad 433=item $self->setup_actions( $class, $component )
1abd6db7 434
435=cut
436
437sub setup_actions {
fbcc39ad 438 my ( $self, $class ) = @_;
99fe1710 439
12e28165 440 # These are the core structures
441 $self->actions(
442 {
443 plain => {},
444 private => {},
445 regex => {},
fbcc39ad 446 compiled => []
12e28165 447 }
448 );
449
450 # We use a tree
b7aebc12 451 my $container = Catalyst::ActionContainer->new(
452 { part => '/', actions => {} } );
453 $self->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
e494bd6b 454
fbcc39ad 455 for my $comp ( keys %{ $class->components } ) {
e494bd6b 456
457 # We only setup components that inherit from Catalyst::Base
a268a011 458 next unless $comp->isa('Catalyst::Base');
99fe1710 459
812a28c9 460 for my $action ( @{ Catalyst::Utils::reflect_actions($comp) } ) {
1abd6db7 461 my ( $code, $attrs ) = @{$action};
462 my $name = '';
463 no strict 'refs';
464 my @cache = ( $comp, @{"$comp\::ISA"} );
465 my %namespaces;
99fe1710 466
1abd6db7 467 while ( my $namespace = shift @cache ) {
468 $namespaces{$namespace}++;
469 for my $isa ( @{"$comp\::ISA"} ) {
470 next if $namespaces{$isa};
471 push @cache, $isa;
472 $namespaces{$isa}++;
473 }
474 }
99fe1710 475
1abd6db7 476 for my $namespace ( keys %namespaces ) {
99fe1710 477
1abd6db7 478 for my $sym ( values %{ $namespace . '::' } ) {
99fe1710 479
1abd6db7 480 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
99fe1710 481
1abd6db7 482 $name = *{$sym}{NAME};
fbcc39ad 483 $class->set_action( $name, $code, $comp, $attrs );
1abd6db7 484 last;
485 }
99fe1710 486
1abd6db7 487 }
99fe1710 488
1abd6db7 489 }
99fe1710 490
1abd6db7 491 }
99fe1710 492
1abd6db7 493 }
e494bd6b 494
fbcc39ad 495 return unless $class->debug;
99fe1710 496
1abd6db7 497 my $actions = $self->actions;
498 my $privates = Text::ASCIITable->new;
5fbed090 499 $privates->setCols( 'Private', 'Class' );
500 $privates->setColWidth( 'Private', 36, 1 );
501 $privates->setColWidth( 'Class', 37, 1 );
99fe1710 502
1abd6db7 503 my $walker = sub {
504 my ( $walker, $parent, $prefix ) = @_;
505 $prefix .= $parent->getNodeValue || '';
506 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 507 my $node = $parent->getNodeValue->actions;
99fe1710 508
b7aebc12 509 for my $action ( keys %{ $node } ) {
510 my $action_obj = $node->{$action};
fbcc39ad 511 $privates->addRow( "$prefix$action", $action_obj->namespace );
1abd6db7 512 }
99fe1710 513
1abd6db7 514 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
515 };
99fe1710 516
1abd6db7 517 $walker->( $walker, $self->tree, '' );
fbcc39ad 518 $class->log->debug( "Loaded private actions:\n" . $privates->draw )
a268a011 519 if ( @{ $privates->{tbl_rows} } );
99fe1710 520
1abd6db7 521 my $publics = Text::ASCIITable->new;
522 $publics->setCols( 'Public', 'Private' );
699e1247 523 $publics->setColWidth( 'Public', 36, 1 );
524 $publics->setColWidth( 'Private', 37, 1 );
99fe1710 525
1abd6db7 526 for my $plain ( sort keys %{ $actions->{plain} } ) {
fbcc39ad 527 my $action = $actions->{plain}->{$plain};
528 $publics->addRow( "/$plain", "/$action" );
1abd6db7 529 }
99fe1710 530
fbcc39ad 531 $class->log->debug( "Loaded public actions:\n" . $publics->draw )
a268a011 532 if ( @{ $publics->{tbl_rows} } );
99fe1710 533
1abd6db7 534 my $regexes = Text::ASCIITable->new;
535 $regexes->setCols( 'Regex', 'Private' );
699e1247 536 $regexes->setColWidth( 'Regex', 36, 1 );
537 $regexes->setColWidth( 'Private', 37, 1 );
99fe1710 538
1abd6db7 539 for my $regex ( sort keys %{ $actions->{regex} } ) {
fbcc39ad 540 my $action = $actions->{regex}->{$regex};
541 $regexes->addRow( $regex, "/$action" );
1abd6db7 542 }
99fe1710 543
fbcc39ad 544 $class->log->debug( "Loaded regex actions:\n" . $regexes->draw )
a268a011 545 if ( @{ $regexes->{tbl_rows} } );
1abd6db7 546}
547
1abd6db7 548=back
549
550=head1 AUTHOR
551
552Sebastian Riedel, C<sri@cpan.org>
553
554=head1 COPYRIGHT
555
556This program is free software, you can redistribute it and/or modify it under
557the same terms as Perl itself.
558
559=cut
560
5611;