Remove internal constants from the Engine::HTTP namespace.
[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->dispatch($c)
1abd6db7 88
4ab87e27 89Delegate the dispatch to the action that matched the url, or return a
90message about unknown resource
91
92
1abd6db7 93=cut
94
95sub dispatch {
fbcc39ad 96 my ( $self, $c ) = @_;
e63bdf38 97 if ( my $action = $c->action ) {
98 $c->forward( join( '/', '', $action->namespace, '_DISPATCH' ) );
fbcc39ad 99 }
100
101 else {
1abd6db7 102 my $path = $c->req->path;
103 my $error = $path
104 ? qq/Unknown resource "$path"/
105 : "No default action defined";
106 $c->log->error($error) if $c->debug;
107 $c->error($error);
108 }
109}
110
2f381252 111# $self->_command2action( $c, $command [, \@arguments ] )
112# Search for an action, from the command and returns C<($action, $args)> on
113# success. Returns C<(0)> on error.
1abd6db7 114
2f381252 115sub _command2action {
e72f8f51 116 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 117
1abd6db7 118 unless ($command) {
2f381252 119 $c->log->debug('Nothing to go to') if $c->debug;
1abd6db7 120 return 0;
121 }
99fe1710 122
e72f8f51 123 my @args;
068c0898 124
e72f8f51 125 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
126 @args = @{ pop @extra_params }
127 } else {
2f381252 128 # this is a copy, it may take some abuse from
129 # ->_invoke_as_path if the path had trailing parts
e72f8f51 130 @args = @{ $c->request->arguments };
131 }
132
133 my $action;
134
2f381252 135 # go to a string path ("/foo/bar/gorch")
136 # or action object which stringifies to that
e72f8f51 137 $action = $self->_invoke_as_path( $c, "$command", \@args );
99fe1710 138
2f381252 139 # go to a component ( "MyApp::*::Foo" or $c->component("...")
140 # - a path or an object)
e72f8f51 141 unless ($action) {
142 my $method = @extra_params ? $extra_params[0] : "process";
143 $action = $self->_invoke_as_component( $c, $command, $method );
144 }
99fe1710 145
2f381252 146 return $action, \@args;
147}
148
ae0e35ee 149=head2 $self->visit( $c, $command [, \@arguments ] )
2f381252 150
151Documented in L<Catalyst>
152
153=cut
154
ae0e35ee 155sub visit {
2f381252 156 my $self = shift;
ae0e35ee 157 $self->_do_visit('visit', @_);
158}
159
160sub _do_visit {
161 my $self = shift;
162 my $opname = shift;
2f381252 163 my ( $c, $command ) = @_;
164 my ( $action, $args ) = $self->_command2action(@_);
ae0e35ee 165 my $error = qq/Couldn't $opname("$command"): /;
2f381252 166
ae0e35ee 167 if (!$action) {
3ea37672 168 $error .= qq/Couldn't $opname to command "$command": /
169 .qq/Invalid action or component./;
ae0e35ee 170 }
171 elsif (!defined $action->namespace) {
172 $error .= qq/Action has no namespace: cannot $opname() to a plain /
173 .qq/method or component, must be a :Action or some sort./
174 }
175 elsif (!$action->class->can('_DISPATCH')) {
176 $error .= qq/Action cannot _DISPATCH. /
177 .qq/Did you try to $opname() a non-controller action?/;
178 }
179 else {
180 $error = q();
181 }
182
183 if($error) {
2f381252 184 $c->error($error);
185 $c->log->debug($error) if $c->debug;
186 return 0;
187 }
188
52f71256 189 $action = $self->expand_action($action);
190
2f381252 191 local $c->request->{arguments} = $args;
ae0e35ee 192 local $c->{namespace} = $action->{'namespace'};
193 local $c->{action} = $action;
194
2f381252 195 $self->dispatch($c);
ae0e35ee 196}
197
198=head2 $self->go( $c, $command [, \@arguments ] )
199
200Documented in L<Catalyst>
201
202=cut
2f381252 203
ae0e35ee 204sub go {
205 my $self = shift;
206 $self->_do_visit('go', @_);
2f381252 207 die $Catalyst::GO;
208}
209
210=head2 $self->forward( $c, $command [, \@arguments ] )
211
212Documented in L<Catalyst>
213
214=cut
215
216sub forward {
217 my $self = shift;
3ea37672 218 $self->_do_forward(forward => @_);
219}
220
221sub _do_forward {
222 my $self = shift;
223 my $opname = shift;
2f381252 224 my ( $c, $command ) = @_;
225 my ( $action, $args ) = $self->_command2action(@_);
99fe1710 226
3ea37672 227 if (!$action) {
228 my $error .= qq/Couldn't $opname to command "$command": /
229 .qq/Invalid action or component./;
e540158b 230 $c->error($error);
231 $c->log->debug($error) if $c->debug;
232 return 0;
233 }
bd7d2e94 234
059c085b 235 no warnings 'recursion';
236
6680c772 237 my $orig_args = $c->request->arguments();
a9078a5a 238 $c->request->arguments($args);
b8f669f3 239 $action->dispatch( $c );
6680c772 240 $c->request->arguments($orig_args);
3ea37672 241
1abd6db7 242 return $c->state;
243}
244
3ea37672 245=head2 $self->detach( $c, $command [, \@arguments ] )
246
247Documented in L<Catalyst>
248
249=cut
250
251sub detach {
252 my ( $self, $c, $command, @args ) = @_;
253 $self->_do_forward(detach => $c, $command, @args ) if $command;
254 die $Catalyst::DETACH;
255}
256
adb53907 257sub _action_rel2abs {
e540158b 258 my ( $self, $c, $path ) = @_;
259
260 unless ( $path =~ m#^/# ) {
261 my $namespace = $c->stack->[-1]->namespace;
262 $path = "$namespace/$path";
263 }
264
265 $path =~ s#^/##;
266 return $path;
adb53907 267}
268
269sub _invoke_as_path {
e540158b 270 my ( $self, $c, $rel_path, $args ) = @_;
271
e540158b 272 my $path = $self->_action_rel2abs( $c, $rel_path );
273
274 my ( $tail, @extra_args );
275 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
276 { # allow $path to be empty
277 if ( my $action = $c->get_action( $tail, $path ) ) {
278 push @$args, @extra_args;
279 return $action;
280 }
281 else {
282 return
283 unless $path
284 ; # if a match on the global namespace failed then the whole lookup failed
285 }
286
287 unshift @extra_args, $tail;
288 }
adb53907 289}
290
291sub _find_component_class {
e540158b 292 my ( $self, $c, $component ) = @_;
adb53907 293
e540158b 294 return ref($component)
295 || ref( $c->component($component) )
296 || $c->component($component);
adb53907 297}
298
299sub _invoke_as_component {
e540158b 300 my ( $self, $c, $component, $method ) = @_;
301
302 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 303
304 if ( my $code = $class->can($method) ) {
059c085b 305 return $self->_method_action_class->new(
e540158b 306 {
307 name => $method,
308 code => $code,
309 reverse => "$class->$method",
310 class => $class,
311 namespace => Catalyst::Utils::class2prefix(
312 $class, $c->config->{case_sensitive}
313 ),
314 }
315 );
316 }
317 else {
318 my $error =
319 qq/Couldn't forward to "$class". Does not implement "$method"/;
320 $c->error($error);
321 $c->log->debug($error)
322 if $c->debug;
323 return 0;
324 }
adb53907 325}
326
b5ecfcf0 327=head2 $self->prepare_action($c)
fbcc39ad 328
4ab87e27 329Find an dispatch type that matches $c->req->path, and set args from it.
330
fbcc39ad 331=cut
332
333sub prepare_action {
334 my ( $self, $c ) = @_;
e63bdf38 335 my $req = $c->req;
336 my $path = $req->path;
337 my @path = split /\//, $req->path;
338 $req->args( \my @args );
fbcc39ad 339
61a9002d 340 unshift( @path, '' ); # Root action
78d760bb 341
b96f127f 342 DESCEND: while (@path) {
fbcc39ad 343 $path = join '/', @path;
61a9002d 344 $path =~ s#^/##;
fbcc39ad 345
61a9002d 346 $path = '' if $path eq '/'; # Root action
78d760bb 347
22f3a8dd 348 # Check out dispatch types to see if any will handle the path at
349 # this level
350
059c085b 351 foreach my $type ( @{ $self->_dispatch_types } ) {
2633d7dc 352 last DESCEND if $type->match( $c, $path );
66e28e3f 353 }
b96f127f 354
22f3a8dd 355 # If not, move the last part path to args
4082e678 356 my $arg = pop(@path);
357 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
358 unshift @args, $arg;
fbcc39ad 359 }
360
e63bdf38 361 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
66d7ad40 362
e63bdf38 363 $c->log->debug( 'Path is "' . $req->match . '"' )
076bfad3 364 if ( $c->debug && defined $req->match && length $req->match );
e3a13771 365
fbcc39ad 366 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
367 if ( $c->debug && @args );
368}
369
b5ecfcf0 370=head2 $self->get_action( $action, $namespace )
1abd6db7 371
4ab87e27 372returns a named action from a given namespace.
373
1abd6db7 374=cut
375
376sub get_action {
bcd1002b 377 my ( $self, $name, $namespace ) = @_;
79a3189a 378 return unless $name;
3d0d6d21 379
2f381252 380 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
99fe1710 381
e63bdf38 382 return $self->_action_hash->{"${namespace}/${name}"};
1abd6db7 383}
384
ac5c933b 385=head2 $self->get_action_by_path( $path );
068c0898 386
ac5c933b 387Returns the named action by its full path.
3d0d6d21 388
068c0898 389=cut
3d0d6d21 390
391sub get_action_by_path {
392 my ( $self, $path ) = @_;
ea0e58d9 393 $path =~ s/^\///;
28928de9 394 $path = "/$path" unless $path =~ /\//;
059c085b 395 $self->_action_hash->{$path};
3d0d6d21 396}
397
b5ecfcf0 398=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 399
400=cut
401
402sub get_actions {
403 my ( $self, $c, $action, $namespace ) = @_;
404 return [] unless $action;
3d0d6d21 405
28928de9 406 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 407
408 my @match = $self->get_containers($namespace);
409
684d10ed 410 return map { $_->get_action($action) } @match;
a9dc674c 411}
412
b5ecfcf0 413=head2 $self->get_containers( $namespace )
cfd04b0c 414
4ab87e27 415Return all the action containers for a given namespace, inclusive
416
cfd04b0c 417=cut
418
419sub get_containers {
420 my ( $self, $namespace ) = @_;
a13e21ab 421 $namespace ||= '';
422 $namespace = '' if $namespace eq '/';
cfd04b0c 423
a13e21ab 424 my @containers;
cfd04b0c 425
7f23827b 426 if ( length $namespace ) {
427 do {
059c085b 428 push @containers, $self->_container_hash->{$namespace};
7f23827b 429 } while ( $namespace =~ s#/[^/]+$## );
430 }
90ce41ba 431
059c085b 432 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 433
e63bdf38 434 #return (split '/', $namespace); # isnt this more clear?
a13e21ab 435 my @parts = split '/', $namespace;
cfd04b0c 436}
437
ea0e58d9 438=head2 $self->uri_for_action($action, \@captures)
439
440Takes a Catalyst::Action object and action parameters and returns a URI
441part such that if $c->req->path were this URI part, this action would be
442dispatched to with $c->req->captures set to the supplied arrayref.
443
444If the action object is not available for external dispatch or the dispatcher
445cannot determine an appropriate URI, this method will return undef.
446
447=cut
448
449sub uri_for_action {
450 my ( $self, $action, $captures) = @_;
451 $captures ||= [];
059c085b 452 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 453 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 454 return( $uri eq '' ? '/' : $uri )
455 if defined($uri);
ea0e58d9 456 }
457 return undef;
458}
459
ae0e35ee 460=head2 expand_action
461
462expand an action into a full representation of the dispatch.
463mostly useful for chained, other actions will just return a
464single action.
465
466=cut
467
52f71256 468sub expand_action {
469 my ($self, $action) = @_;
470
471 foreach my $dispatch_type (@{ $self->_dispatch_types }) {
472 my $expanded = $dispatch_type->expand_action($action);
473 return $expanded if $expanded;
474 }
475
476 return $action;
477}
478
b5ecfcf0 479=head2 $self->register( $c, $action )
aad72cc9 480
4ab87e27 481Make sure all required dispatch types for this action are loaded, then
482pass the action to our dispatch types so they can register it if required.
483Also, set up the tree with the action containers.
484
aad72cc9 485=cut
486
79a3189a 487sub register {
488 my ( $self, $c, $action ) = @_;
489
059c085b 490 my $registered = $self->_registered_dispatch_types;
694d15f1 491
e63bdf38 492 #my $priv = 0; #seems to be unused
694d15f1 493 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 494 next if $key eq 'Private';
694d15f1 495 my $class = "Catalyst::DispatchType::$key";
496 unless ( $registered->{$class} ) {
5fb67d52 497 #some error checking rethrowing here wouldn't hurt.
068c0898 498 eval { Class::MOP::load_class($class) };
059c085b 499 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 500 $registered->{$class} = 1;
501 }
502 }
503
504 # Pass the action to our dispatch types so they can register it if reqd.
059c085b 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
059c085b 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
059c085b 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
12e28165 557
9e81ba44 558 my @classes =
ad5e4650 559 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
059c085b 560 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 561
49070d25 562 foreach my $comp ( values %{ $c->components } ) {
563 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 564 }
e494bd6b 565
ad5e4650 566 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 567
11bd4e3e 568 return unless $c->debug;
99fe1710 569
684d10ed 570 my $privates = Text::SimpleTable->new(
dbf03873 571 [ 20, 'Private' ],
34d28dfd 572 [ 36, 'Class' ],
dbf03873 573 [ 12, 'Method' ]
684d10ed 574 );
99fe1710 575
87b85407 576 my $has_private = 0;
1abd6db7 577 my $walker = sub {
578 my ( $walker, $parent, $prefix ) = @_;
579 $prefix .= $parent->getNodeValue || '';
580 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 581 my $node = $parent->getNodeValue->actions;
99fe1710 582
78d760bb 583 for my $action ( keys %{$node} ) {
b7aebc12 584 my $action_obj = $node->{$action};
b0bb11ec 585 next
586 if ( ( $action =~ /^_.*/ )
587 && ( !$c->config->{show_internal_actions} ) );
684d10ed 588 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 589 $has_private = 1;
1abd6db7 590 }
99fe1710 591
1abd6db7 592 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
593 };
99fe1710 594
059c085b 595 $walker->( $walker, $self->_tree, '' );
1cf0345b 596 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
597 if $has_private;
99fe1710 598
a9cbd748 599 # List all public actions
059c085b 600 $_->list($c) for @{ $self->_dispatch_types };
1abd6db7 601}
602
ad5e4650 603sub _load_dispatch_types {
9e81ba44 604 my ( $self, @types ) = @_;
605
606 my @loaded;
607
608 # Preload action types
609 for my $type (@types) {
610 my $class =
611 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
0fc2d522 612
068c0898 613 eval { Class::MOP::load_class($class) };
9e81ba44 614 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
615 if $@;
059c085b 616 push @{ $self->_dispatch_types }, $class->new;
9e81ba44 617
618 push @loaded, $class;
619 }
620
a13e21ab 621 return @loaded;
9e81ba44 622}
623
6680c772 624no Moose;
e5ecd5bc 625__PACKAGE__->meta->make_immutable;
626
059c085b 627=head2 meta
628
629Provided by Moose
630
2f381252 631=head1 AUTHORS
1abd6db7 632
2f381252 633Catalyst Contributors, see Catalyst.pm
1abd6db7 634
635=head1 COPYRIGHT
636
637This program is free software, you can redistribute it and/or modify it under
638the same terms as Perl itself.
639
640=cut
641
6421;