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