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