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