- Start of DispatchType refactor
[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
223 # It's a regex
224 if ($#$result) {
225 my $match = $result->[1];
226 my @snippets = @{ $result->[2] };
227 $c->log->debug(
228 qq/Requested action is "$path" and matched "$match"/)
229 if $c->debug;
230 $c->log->debug(
231 'Snippets are "' . join( ' ', @snippets ) . '"' )
232 if ( $c->debug && @snippets );
233 $c->req->action($match);
234 $c->req->snippets( \@snippets );
235 }
236
237 else {
238 $c->req->action($path);
239 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
240 }
1abd6db7 241
fbcc39ad 242 $c->req->match($path);
66e28e3f 243 $c->action($result->[0]);
244 $c->namespace($result->[0]->prefix);
b96f127f 245 last DESCEND;
fbcc39ad 246 }
fbcc39ad 247
b96f127f 248 unless ( $c->action ) {
249 foreach my $type (@{$self->dispatch_types}) {
250 last DESCEND if $type->prepare_action($c, $path);
251 #last DESCEND if $c->action;
252 }
66e28e3f 253 }
b96f127f 254
255 unshift @args, pop @path;
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] ] }
b96f127f 286 #elsif ( my $r = $self->actions->{regex}->{$action} ) { return [ [$r] ] }
99fe1710 287
b96f127f 288 #else {
99fe1710 289
b96f127f 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
b96f127f 294 # if ( my @snippets = ( $action =~ $regex ) ) {
295 # return [
296 # [ $self->actions->{regex}->{$name}, $name, \@snippets ] ];
297 # }
99fe1710 298
b96f127f 299 # }
300 #}
1abd6db7 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;
b96f127f 356 my %attributes;
1abd6db7 357
358 for my $attr ( @{$attrs} ) {
0299ba22 359 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
360 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
749472d6 361 elsif ( $attr =~ /^Path\(\s*(.+)\s*\)$/i ) {
362 push @{ $flags{path} }, $1;
363 }
364 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
0299ba22 365 elsif ( $attr =~ /^(Regex|Regexp)\(\s*(.+)\s*\)$/i ) {
749472d6 366 push @{ $flags{regex} }, $2;
0299ba22 367 }
b96f127f 368 if ( my ($key, $value) = ($attr =~ /^(.*?)(?:\(\s*(.+)\s*\))?$/) ) {
369 if ( defined $value ) {
370 ($value =~ s/^'(.*)'$/$1/) || ($value =~ s/^"(.*)"/$1/);
371 }
372 push(@{$attributes{$key}}, $value);
373 }
1abd6db7 374 }
375
d0e524d2 376 if ( $flags{private} && ( keys %flags > 1 ) ) {
8d4e224b 377 $c->log->debug( 'Bad action definition "'
d0e524d2 378 . join( ' ', @{$attrs} )
8d4e224b 379 . qq/" for "$namespace->$method"/ )
380 if $c->debug;
d0e524d2 381 return;
382 }
1abd6db7 383 return unless keys %flags;
384
fbcc39ad 385 my $parent = $self->tree;
1abd6db7 386 my $visitor = Tree::Simple::Visitor::FindByPath->new;
99fe1710 387
b7aebc12 388 if ($prefix) {
389 for my $part ( split '/', $prefix ) {
1abd6db7 390 $visitor->setSearchPath($part);
391 $parent->accept($visitor);
b7aebc12 392 my $child = $visitor->getResult;
393
394 unless ($child) {
90ce41ba 395
396 # Create a new tree node and an ActionContainer to form
397 # its value.
398
b7aebc12 399 my $container = Catalyst::ActionContainer->new(
400 { part => $part, actions => {} });
401 $child = $parent->addChild( Tree::Simple->new($container) );
402 $visitor->setSearchPath($part);
403 $parent->accept($visitor);
404 $child = $visitor->getResult;
405 }
406
407 $parent = $child;
1abd6db7 408 }
1abd6db7 409 }
99fe1710 410
fbcc39ad 411 my $reverse = $prefix ? "$prefix/$method" : $method;
412
413 my $action = Catalyst::Action->new(
414 {
b96f127f 415 code => $code,
416 reverse => $reverse,
417 namespace => $namespace,
418 prefix => $prefix,
419 attributes => \%attributes,
fbcc39ad 420 }
421 );
422
90ce41ba 423 # Set the method value
b7aebc12 424 $parent->getNodeValue->actions->{$method} = $action;
fbcc39ad 425
749472d6 426 my @path;
427 for my $path ( @{ $flags{path} } ) {
428 $path =~ s/^\w+//;
429 $path =~ s/\w+$//;
430 if ( $path =~ /^\s*'(.*)'\s*$/ ) { $path = $1 }
431 if ( $path =~ /^\s*"(.*)"\s*$/ ) { $path = $1 }
432 push @path, $path;
1abd6db7 433 }
749472d6 434 $flags{path} = \@path;
435
436 my @regex;
437 for my $regex ( @{ $flags{regex} } ) {
438 $regex =~ s/^\w+//;
439 $regex =~ s/\w+$//;
440 if ( $regex =~ /^\s*'(.*)'\s*$/ ) { $regex = $1 }
441 if ( $regex =~ /^\s*"(.*)"\s*$/ ) { $regex = $1 }
442 push @regex, $regex;
1abd6db7 443 }
749472d6 444 $flags{regex} = \@regex;
1abd6db7 445
749472d6 446 if ( $flags{local} || $flags{global} ) {
447 push( @{ $flags{path} }, $prefix ? "/$prefix/$method" : "/$method" )
448 if $flags{local};
99fe1710 449
749472d6 450 push( @{ $flags{path} }, "/$method" ) if $flags{global};
451 }
99fe1710 452
749472d6 453 for my $path ( @{ $flags{path} } ) {
454 if ( $path =~ /^\// ) { $path =~ s/^\/// }
455 else { $path = $prefix ? "$prefix/$path" : $path }
456 $self->actions->{plain}->{$path} = $action;
1abd6db7 457 }
99fe1710 458
749472d6 459 for my $regex ( @{ $flags{regex} } ) {
fbcc39ad 460 push @{ $self->actions->{compiled} }, [ $regex, qr#$regex# ];
461 $self->actions->{regex}->{$regex} = $action;
1abd6db7 462 }
b96f127f 463
464 foreach my $type ( @{ $self->dispatch_types } ) {
465 $type->register_action($c, $action);
466 }
1abd6db7 467}
468
fbcc39ad 469=item $self->setup_actions( $class, $component )
1abd6db7 470
471=cut
472
473sub setup_actions {
fbcc39ad 474 my ( $self, $class ) = @_;
99fe1710 475
12e28165 476 # These are the core structures
477 $self->actions(
478 {
479 plain => {},
480 private => {},
481 regex => {},
fbcc39ad 482 compiled => []
12e28165 483 }
484 );
485
b96f127f 486 $self->dispatch_types([
487 map { "Catalyst::DispatchType::$_"->new }
488 qw/Regex Default/ ]);
489
12e28165 490 # We use a tree
b7aebc12 491 my $container = Catalyst::ActionContainer->new(
492 { part => '/', actions => {} } );
493 $self->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
e494bd6b 494
fbcc39ad 495 for my $comp ( keys %{ $class->components } ) {
e494bd6b 496
497 # We only setup components that inherit from Catalyst::Base
a268a011 498 next unless $comp->isa('Catalyst::Base');
99fe1710 499
812a28c9 500 for my $action ( @{ Catalyst::Utils::reflect_actions($comp) } ) {
1abd6db7 501 my ( $code, $attrs ) = @{$action};
502 my $name = '';
503 no strict 'refs';
504 my @cache = ( $comp, @{"$comp\::ISA"} );
505 my %namespaces;
99fe1710 506
1abd6db7 507 while ( my $namespace = shift @cache ) {
508 $namespaces{$namespace}++;
509 for my $isa ( @{"$comp\::ISA"} ) {
510 next if $namespaces{$isa};
511 push @cache, $isa;
512 $namespaces{$isa}++;
513 }
514 }
99fe1710 515
1abd6db7 516 for my $namespace ( keys %namespaces ) {
99fe1710 517
1abd6db7 518 for my $sym ( values %{ $namespace . '::' } ) {
99fe1710 519
1abd6db7 520 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
99fe1710 521
1abd6db7 522 $name = *{$sym}{NAME};
fbcc39ad 523 $class->set_action( $name, $code, $comp, $attrs );
1abd6db7 524 last;
525 }
99fe1710 526
1abd6db7 527 }
99fe1710 528
1abd6db7 529 }
99fe1710 530
1abd6db7 531 }
99fe1710 532
1abd6db7 533 }
e494bd6b 534
fbcc39ad 535 return unless $class->debug;
99fe1710 536
1abd6db7 537 my $actions = $self->actions;
538 my $privates = Text::ASCIITable->new;
5fbed090 539 $privates->setCols( 'Private', 'Class' );
540 $privates->setColWidth( 'Private', 36, 1 );
541 $privates->setColWidth( 'Class', 37, 1 );
99fe1710 542
1abd6db7 543 my $walker = sub {
544 my ( $walker, $parent, $prefix ) = @_;
545 $prefix .= $parent->getNodeValue || '';
546 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 547 my $node = $parent->getNodeValue->actions;
99fe1710 548
b7aebc12 549 for my $action ( keys %{ $node } ) {
550 my $action_obj = $node->{$action};
fbcc39ad 551 $privates->addRow( "$prefix$action", $action_obj->namespace );
1abd6db7 552 }
99fe1710 553
1abd6db7 554 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
555 };
99fe1710 556
1abd6db7 557 $walker->( $walker, $self->tree, '' );
fbcc39ad 558 $class->log->debug( "Loaded private actions:\n" . $privates->draw )
a268a011 559 if ( @{ $privates->{tbl_rows} } );
99fe1710 560
1abd6db7 561 my $publics = Text::ASCIITable->new;
562 $publics->setCols( 'Public', 'Private' );
699e1247 563 $publics->setColWidth( 'Public', 36, 1 );
564 $publics->setColWidth( 'Private', 37, 1 );
99fe1710 565
1abd6db7 566 for my $plain ( sort keys %{ $actions->{plain} } ) {
fbcc39ad 567 my $action = $actions->{plain}->{$plain};
568 $publics->addRow( "/$plain", "/$action" );
1abd6db7 569 }
99fe1710 570
fbcc39ad 571 $class->log->debug( "Loaded public actions:\n" . $publics->draw )
a268a011 572 if ( @{ $publics->{tbl_rows} } );
99fe1710 573
1abd6db7 574 my $regexes = Text::ASCIITable->new;
575 $regexes->setCols( 'Regex', 'Private' );
699e1247 576 $regexes->setColWidth( 'Regex', 36, 1 );
577 $regexes->setColWidth( 'Private', 37, 1 );
99fe1710 578
1abd6db7 579 for my $regex ( sort keys %{ $actions->{regex} } ) {
fbcc39ad 580 my $action = $actions->{regex}->{$regex};
581 $regexes->addRow( $regex, "/$action" );
1abd6db7 582 }
99fe1710 583
fbcc39ad 584 $class->log->debug( "Loaded regex actions:\n" . $regexes->draw )
a268a011 585 if ( @{ $regexes->{tbl_rows} } );
1abd6db7 586}
587
1abd6db7 588=back
589
590=head1 AUTHOR
591
592Sebastian Riedel, C<sri@cpan.org>
593
594=head1 COPYRIGHT
595
596This program is free software, you can redistribute it and/or modify it under
597the same terms as Perl itself.
598
599=cut
600
6011;