back out go() so we can ship a 5.7100 with other features and bugfixes
[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::Default;
bcccee4e 10use Catalyst::DispatchType::Index;
87b85407 11use Text::SimpleTable;
1abd6db7 12use Tree::Simple;
13use Tree::Simple::Visitor::FindByPath;
e72f8f51 14use Scalar::Util ();
1abd6db7 15
fbcc39ad 16# Stringify to class
17use overload '""' => sub { return ref shift }, fallback => 1;
18
49070d25 19__PACKAGE__->mk_accessors(
20 qw/tree dispatch_types registered_dispatch_types
e7bb8d33 21 method_action_class action_container_class
22 preload_dispatch_types postload_dispatch_types
a13e21ab 23 action_hash container_hash
9e81ba44 24 /
49070d25 25);
6d030e6f 26
27# Preload these action types
61a9002d 28our @PRELOAD = qw/Index Path Regex/;
1abd6db7 29
2d1d8f91 30# Postload these action types
61a9002d 31our @POSTLOAD = qw/Default/;
2d1d8f91 32
1abd6db7 33=head1 NAME
34
9c053379 35Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 36
37=head1 SYNOPSIS
38
39See L<Catalyst>.
40
41=head1 DESCRIPTION
42
4ab87e27 43This is the class that maps public urls to actions in your Catalyst
44application based on the attributes you set.
45
1abd6db7 46=head1 METHODS
47
76ddf86b 48=head2 new
4ab87e27 49
50Construct a new dispatcher.
51
e7bb8d33 52=cut
53
54sub new {
9e81ba44 55 my $self = shift;
e7bb8d33 56 my $class = ref($self) || $self;
9e81ba44 57
58 my $obj = $class->SUPER::new(@_);
59
e7bb8d33 60 # set the default pre- and and postloads
9e81ba44 61 $obj->preload_dispatch_types( \@PRELOAD );
e7bb8d33 62 $obj->postload_dispatch_types( \@POSTLOAD );
a13e21ab 63 $obj->action_hash( {} );
64 $obj->container_hash( {} );
65
66 # Create the root node of the tree
67 my $container =
68 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
69 $obj->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
70
9e81ba44 71 return $obj;
e7bb8d33 72}
73
74=head2 $self->preload_dispatch_types
75
76An arrayref of pre-loaded dispatchtype classes
77
78Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
79To use a custom class outside the regular C<Catalyst> namespace, prefix
80it with a C<+>, like so:
81
82 +My::Dispatch::Type
83
84=head2 $self->postload_dispatch_types
85
86An arrayref of post-loaded dispatchtype classes
87
88Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
89To use a custom class outside the regular C<Catalyst> namespace, prefix
90it with a C<+>, like so:
91
92 +My::Dispatch::Type
93
b5ecfcf0 94=head2 $self->detach( $c, $command [, \@arguments ] )
6ef62eb2 95
4ab87e27 96Documented in L<Catalyst>
97
6ef62eb2 98=cut
99
100sub detach {
fbcc39ad 101 my ( $self, $c, $command, @args ) = @_;
bd7d2e94 102 $c->forward( $command, @args ) if $command;
fbcc39ad 103 die $Catalyst::DETACH;
6ef62eb2 104}
105
b5ecfcf0 106=head2 $self->dispatch($c)
1abd6db7 107
4ab87e27 108Delegate the dispatch to the action that matched the url, or return a
109message about unknown resource
110
111
1abd6db7 112=cut
113
114sub dispatch {
fbcc39ad 115 my ( $self, $c ) = @_;
66e28e3f 116 if ( $c->action ) {
28591cd7 117 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
fbcc39ad 118 }
119
120 else {
1abd6db7 121 my $path = $c->req->path;
122 my $error = $path
123 ? qq/Unknown resource "$path"/
124 : "No default action defined";
125 $c->log->error($error) if $c->debug;
126 $c->error($error);
127 }
128}
129
9bfaf006 130# $self->_command2action( $c, $command [, \@arguments ] )
131# Search for an action, from the command and returns C<($action, $args)> on
132# success. Returns C<(0)> on error.
1abd6db7 133
9bfaf006 134sub _command2action {
e72f8f51 135 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 136
1abd6db7 137 unless ($command) {
9bfaf006 138 $c->log->debug('Nothing to go to') if $c->debug;
1abd6db7 139 return 0;
140 }
99fe1710 141
e72f8f51 142 my @args;
143
144 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
145 @args = @{ pop @extra_params }
146 } else {
9bfaf006 147 # this is a copy, it may take some abuse from
148 # ->_invoke_as_path if the path had trailing parts
e72f8f51 149 @args = @{ $c->request->arguments };
150 }
151
152 my $action;
153
9bfaf006 154 # go to a string path ("/foo/bar/gorch")
155 # or action object which stringifies to that
e72f8f51 156 $action = $self->_invoke_as_path( $c, "$command", \@args );
99fe1710 157
9bfaf006 158 # go to a component ( "MyApp::*::Foo" or $c->component("...")
159 # - a path or an object)
e72f8f51 160 unless ($action) {
161 my $method = @extra_params ? $extra_params[0] : "process";
162 $action = $self->_invoke_as_component( $c, $command, $method );
163 }
99fe1710 164
9bfaf006 165 return $action, \@args;
166}
167
9bfaf006 168=head2 $self->forward( $c, $command [, \@arguments ] )
169
170Documented in L<Catalyst>
171
172=cut
173
174sub forward {
175 my $self = shift;
176 my ( $c, $command ) = @_;
177 my ( $action, $args ) = $self->_command2action(@_);
178
179 unless ($action) {
180 my $error =
181 qq/Couldn't forward to command "$command": /
182 . qq/Invalid action or component./;
183 $c->error($error);
184 $c->log->debug($error) if $c->debug;
185 return 0;
186 }
adb53907 187
9bfaf006 188 local $c->request->{arguments} = $args;
b8f669f3 189 $action->dispatch( $c );
99fe1710 190
1abd6db7 191 return $c->state;
192}
193
adb53907 194sub _action_rel2abs {
e540158b 195 my ( $self, $c, $path ) = @_;
196
197 unless ( $path =~ m#^/# ) {
198 my $namespace = $c->stack->[-1]->namespace;
199 $path = "$namespace/$path";
200 }
201
202 $path =~ s#^/##;
203 return $path;
adb53907 204}
205
206sub _invoke_as_path {
e540158b 207 my ( $self, $c, $rel_path, $args ) = @_;
208
e540158b 209 my $path = $self->_action_rel2abs( $c, $rel_path );
210
211 my ( $tail, @extra_args );
212 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
213 { # allow $path to be empty
214 if ( my $action = $c->get_action( $tail, $path ) ) {
215 push @$args, @extra_args;
216 return $action;
217 }
218 else {
219 return
220 unless $path
221 ; # if a match on the global namespace failed then the whole lookup failed
222 }
223
224 unshift @extra_args, $tail;
225 }
adb53907 226}
227
228sub _find_component_class {
e540158b 229 my ( $self, $c, $component ) = @_;
adb53907 230
e540158b 231 return ref($component)
232 || ref( $c->component($component) )
233 || $c->component($component);
adb53907 234}
235
236sub _invoke_as_component {
e540158b 237 my ( $self, $c, $component, $method ) = @_;
238
239 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 240
241 if ( my $code = $class->can($method) ) {
242 return $self->method_action_class->new(
243 {
244 name => $method,
245 code => $code,
246 reverse => "$class->$method",
247 class => $class,
248 namespace => Catalyst::Utils::class2prefix(
249 $class, $c->config->{case_sensitive}
250 ),
251 }
252 );
253 }
254 else {
255 my $error =
256 qq/Couldn't forward to "$class". Does not implement "$method"/;
257 $c->error($error);
258 $c->log->debug($error)
259 if $c->debug;
260 return 0;
261 }
adb53907 262}
263
b5ecfcf0 264=head2 $self->prepare_action($c)
fbcc39ad 265
4ab87e27 266Find an dispatch type that matches $c->req->path, and set args from it.
267
fbcc39ad 268=cut
269
270sub prepare_action {
271 my ( $self, $c ) = @_;
272 my $path = $c->req->path;
273 my @path = split /\//, $c->req->path;
274 $c->req->args( \my @args );
275
61a9002d 276 unshift( @path, '' ); # Root action
78d760bb 277
b96f127f 278 DESCEND: while (@path) {
fbcc39ad 279 $path = join '/', @path;
61a9002d 280 $path =~ s#^/##;
fbcc39ad 281
61a9002d 282 $path = '' if $path eq '/'; # Root action
78d760bb 283
22f3a8dd 284 # Check out dispatch types to see if any will handle the path at
285 # this level
286
78d760bb 287 foreach my $type ( @{ $self->dispatch_types } ) {
2633d7dc 288 last DESCEND if $type->match( $c, $path );
66e28e3f 289 }
b96f127f 290
22f3a8dd 291 # If not, move the last part path to args
4082e678 292 my $arg = pop(@path);
293 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
294 unshift @args, $arg;
fbcc39ad 295 }
296
cccc8f68 297 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$c->req->captures||[]};
66d7ad40 298
e3a13771 299 $c->log->debug( 'Path is "' . $c->req->match . '"' )
53119b78 300 if ( $c->debug && length $c->req->match );
e3a13771 301
fbcc39ad 302 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
303 if ( $c->debug && @args );
304}
305
b5ecfcf0 306=head2 $self->get_action( $action, $namespace )
1abd6db7 307
4ab87e27 308returns a named action from a given namespace.
309
1abd6db7 310=cut
311
312sub get_action {
bcd1002b 313 my ( $self, $name, $namespace ) = @_;
79a3189a 314 return unless $name;
3d0d6d21 315
53119b78 316 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
99fe1710 317
a13e21ab 318 return $self->action_hash->{"$namespace/$name"};
1abd6db7 319}
320
34d28dfd 321=head2 $self->get_action_by_path( $path );
322
323Returns the named action by its full path.
3d0d6d21 324
34d28dfd 325=cut
3d0d6d21 326
327sub get_action_by_path {
328 my ( $self, $path ) = @_;
ea0e58d9 329 $path =~ s/^\///;
28928de9 330 $path = "/$path" unless $path =~ /\//;
3d0d6d21 331 $self->action_hash->{$path};
332}
333
b5ecfcf0 334=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 335
336=cut
337
338sub get_actions {
339 my ( $self, $c, $action, $namespace ) = @_;
340 return [] unless $action;
3d0d6d21 341
28928de9 342 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 343
344 my @match = $self->get_containers($namespace);
345
684d10ed 346 return map { $_->get_action($action) } @match;
a9dc674c 347}
348
b5ecfcf0 349=head2 $self->get_containers( $namespace )
cfd04b0c 350
4ab87e27 351Return all the action containers for a given namespace, inclusive
352
cfd04b0c 353=cut
354
355sub get_containers {
356 my ( $self, $namespace ) = @_;
a13e21ab 357 $namespace ||= '';
358 $namespace = '' if $namespace eq '/';
cfd04b0c 359
a13e21ab 360 my @containers;
cfd04b0c 361
7f23827b 362 if ( length $namespace ) {
363 do {
364 push @containers, $self->container_hash->{$namespace};
365 } while ( $namespace =~ s#/[^/]+$## );
366 }
90ce41ba 367
a13e21ab 368 return reverse grep { defined } @containers, $self->container_hash->{''};
90ce41ba 369
a13e21ab 370 my @parts = split '/', $namespace;
cfd04b0c 371}
372
ea0e58d9 373=head2 $self->uri_for_action($action, \@captures)
374
375Takes a Catalyst::Action object and action parameters and returns a URI
376part such that if $c->req->path were this URI part, this action would be
377dispatched to with $c->req->captures set to the supplied arrayref.
378
379If the action object is not available for external dispatch or the dispatcher
380cannot determine an appropriate URI, this method will return undef.
381
382=cut
383
384sub uri_for_action {
385 my ( $self, $action, $captures) = @_;
386 $captures ||= [];
387 foreach my $dispatch_type ( @{ $self->dispatch_types } ) {
388 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 389 return( $uri eq '' ? '/' : $uri )
390 if defined($uri);
ea0e58d9 391 }
392 return undef;
393}
394
b5ecfcf0 395=head2 $self->register( $c, $action )
aad72cc9 396
4ab87e27 397Make sure all required dispatch types for this action are loaded, then
398pass the action to our dispatch types so they can register it if required.
399Also, set up the tree with the action containers.
400
aad72cc9 401=cut
402
79a3189a 403sub register {
404 my ( $self, $c, $action ) = @_;
405
694d15f1 406 my $registered = $self->registered_dispatch_types;
407
408 my $priv = 0;
409 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 410 next if $key eq 'Private';
694d15f1 411 my $class = "Catalyst::DispatchType::$key";
412 unless ( $registered->{$class} ) {
413 eval "require $class";
414 push( @{ $self->dispatch_types }, $class->new ) unless $@;
415 $registered->{$class} = 1;
416 }
417 }
418
419 # Pass the action to our dispatch types so they can register it if reqd.
01ce0928 420 foreach my $type ( @{ $self->dispatch_types } ) {
9a6ecf4f 421 $type->register( $c, $action );
694d15f1 422 }
423
79a3189a 424 my $namespace = $action->namespace;
a13e21ab 425 my $name = $action->name;
c7116517 426
ad5e4650 427 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 428
429 # Set the method value
a13e21ab 430 $container->add_action($action);
c7116517 431
a13e21ab 432 $self->action_hash->{"$namespace/$name"} = $action;
433 $self->container_hash->{$namespace} = $container;
15e9b5dd 434}
435
ad5e4650 436sub _find_or_create_action_container {
a13e21ab 437 my ( $self, $namespace ) = @_;
438
439 my $tree ||= $self->tree;
99fe1710 440
a13e21ab 441 return $tree->getNodeValue unless $namespace;
78d760bb 442
a13e21ab 443 my @namespace = split '/', $namespace;
444 return $self->_find_or_create_namespace_node( $tree, @namespace )
445 ->getNodeValue;
8505565b 446}
90ce41ba 447
8505565b 448sub _find_or_create_namespace_node {
a13e21ab 449 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 450
a13e21ab 451 return $parent unless $part;
8505565b 452
a13e21ab 453 my $child =
454 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 455
a13e21ab 456 unless ($child) {
457 my $container = Catalyst::ActionContainer->new($part);
458 $parent->addChild( $child = Tree::Simple->new($container) );
459 }
99fe1710 460
a13e21ab 461 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 462}
463
4ab87e27 464=head2 $self->setup_actions( $class, $context )
465
1abd6db7 466
467=cut
468
469sub setup_actions {
11bd4e3e 470 my ( $self, $c ) = @_;
99fe1710 471
6d030e6f 472 $self->dispatch_types( [] );
91d4abc5 473 $self->registered_dispatch_types( {} );
49070d25 474 $self->method_action_class('Catalyst::Action');
475 $self->action_container_class('Catalyst::ActionContainer');
12e28165 476
9e81ba44 477 my @classes =
ad5e4650 478 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
9e81ba44 479 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 480
49070d25 481 foreach my $comp ( values %{ $c->components } ) {
482 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 483 }
e494bd6b 484
ad5e4650 485 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 486
11bd4e3e 487 return unless $c->debug;
99fe1710 488
684d10ed 489 my $privates = Text::SimpleTable->new(
dbf03873 490 [ 20, 'Private' ],
34d28dfd 491 [ 36, 'Class' ],
dbf03873 492 [ 12, 'Method' ]
684d10ed 493 );
99fe1710 494
87b85407 495 my $has_private = 0;
1abd6db7 496 my $walker = sub {
497 my ( $walker, $parent, $prefix ) = @_;
498 $prefix .= $parent->getNodeValue || '';
499 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 500 my $node = $parent->getNodeValue->actions;
99fe1710 501
78d760bb 502 for my $action ( keys %{$node} ) {
b7aebc12 503 my $action_obj = $node->{$action};
b0bb11ec 504 next
505 if ( ( $action =~ /^_.*/ )
506 && ( !$c->config->{show_internal_actions} ) );
684d10ed 507 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 508 $has_private = 1;
1abd6db7 509 }
99fe1710 510
1abd6db7 511 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
512 };
99fe1710 513
1abd6db7 514 $walker->( $walker, $self->tree, '' );
1cf0345b 515 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
516 if $has_private;
99fe1710 517
a9cbd748 518 # List all public actions
11bd4e3e 519 $_->list($c) for @{ $self->dispatch_types };
1abd6db7 520}
521
ad5e4650 522sub _load_dispatch_types {
9e81ba44 523 my ( $self, @types ) = @_;
524
525 my @loaded;
526
527 # Preload action types
528 for my $type (@types) {
529 my $class =
530 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
531 eval "require $class";
532 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
533 if $@;
534 push @{ $self->dispatch_types }, $class->new;
535
536 push @loaded, $class;
537 }
538
a13e21ab 539 return @loaded;
9e81ba44 540}
541
0bf7ab71 542=head1 AUTHORS
1abd6db7 543
0bf7ab71 544Catalyst Contributors, see Catalyst.pm
1abd6db7 545
546=head1 COPYRIGHT
547
548This program is free software, you can redistribute it and/or modify it under
549the same terms as Perl itself.
550
551=cut
552
5531;