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