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