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