mro compat stuff
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
6f1f968a 3use MRO::Compat;
4use mro 'c3';
059c085b 5use Moose;
068c0898 6use Class::MOP;
059c085b 7
a2f2cde9 8use Catalyst::Exception;
f05af9ba 9use Catalyst::Utils;
fbcc39ad 10use Catalyst::Action;
b7aebc12 11use Catalyst::ActionContainer;
b96f127f 12use Catalyst::DispatchType::Default;
bcccee4e 13use Catalyst::DispatchType::Index;
87b85407 14use Text::SimpleTable;
1abd6db7 15use Tree::Simple;
16use Tree::Simple::Visitor::FindByPath;
e72f8f51 17use Scalar::Util ();
1abd6db7 18
5fb12dbb 19#do these belong as package vars or should we build these via a builder method?
6d030e6f 20# Preload these action types
61a9002d 21our @PRELOAD = qw/Index Path Regex/;
1abd6db7 22
2d1d8f91 23# Postload these action types
61a9002d 24our @POSTLOAD = qw/Default/;
2d1d8f91 25
5fb12dbb 26has _tree => (is => 'rw');
8c80e4f8 27has _dispatch_types => (is => 'rw', default => sub { [] }, required => 1, lazy => 1);
28has _registered_dispatch_types => (is => 'rw', default => sub { {} }, required => 1, lazy => 1);
29has _method_action_class => (is => 'rw', default => 'Catalyst::Action');
30has _action_container_class => (is => 'rw', default => 'Catalyst::ActionContainer');
31
5fb12dbb 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 { {} });
059c085b 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
ac5c933b 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
6680c772 173 my $orig_args = $c->request->arguments();
174 $c->request->arguments(\@args);
b8f669f3 175 $action->dispatch( $c );
6680c772 176 $c->request->arguments($orig_args);
177
1abd6db7 178 return $c->state;
179}
180
adb53907 181sub _action_rel2abs {
e540158b 182 my ( $self, $c, $path ) = @_;
183
184 unless ( $path =~ m#^/# ) {
185 my $namespace = $c->stack->[-1]->namespace;
186 $path = "$namespace/$path";
187 }
188
189 $path =~ s#^/##;
190 return $path;
adb53907 191}
192
193sub _invoke_as_path {
e540158b 194 my ( $self, $c, $rel_path, $args ) = @_;
195
e540158b 196 my $path = $self->_action_rel2abs( $c, $rel_path );
197
198 my ( $tail, @extra_args );
199 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
200 { # allow $path to be empty
201 if ( my $action = $c->get_action( $tail, $path ) ) {
202 push @$args, @extra_args;
203 return $action;
204 }
205 else {
206 return
207 unless $path
208 ; # if a match on the global namespace failed then the whole lookup failed
209 }
210
211 unshift @extra_args, $tail;
212 }
adb53907 213}
214
215sub _find_component_class {
e540158b 216 my ( $self, $c, $component ) = @_;
adb53907 217
e540158b 218 return ref($component)
219 || ref( $c->component($component) )
220 || $c->component($component);
adb53907 221}
222
223sub _invoke_as_component {
e540158b 224 my ( $self, $c, $component, $method ) = @_;
225
226 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 227
228 if ( my $code = $class->can($method) ) {
059c085b 229 return $self->_method_action_class->new(
e540158b 230 {
231 name => $method,
232 code => $code,
233 reverse => "$class->$method",
234 class => $class,
235 namespace => Catalyst::Utils::class2prefix(
236 $class, $c->config->{case_sensitive}
237 ),
238 }
239 );
240 }
241 else {
242 my $error =
243 qq/Couldn't forward to "$class". Does not implement "$method"/;
244 $c->error($error);
245 $c->log->debug($error)
246 if $c->debug;
247 return 0;
248 }
adb53907 249}
250
b5ecfcf0 251=head2 $self->prepare_action($c)
fbcc39ad 252
4ab87e27 253Find an dispatch type that matches $c->req->path, and set args from it.
254
fbcc39ad 255=cut
256
257sub prepare_action {
258 my ( $self, $c ) = @_;
e63bdf38 259 my $req = $c->req;
260 my $path = $req->path;
261 my @path = split /\//, $req->path;
262 $req->args( \my @args );
fbcc39ad 263
61a9002d 264 unshift( @path, '' ); # Root action
78d760bb 265
b96f127f 266 DESCEND: while (@path) {
fbcc39ad 267 $path = join '/', @path;
61a9002d 268 $path =~ s#^/##;
fbcc39ad 269
61a9002d 270 $path = '' if $path eq '/'; # Root action
78d760bb 271
22f3a8dd 272 # Check out dispatch types to see if any will handle the path at
273 # this level
274
059c085b 275 foreach my $type ( @{ $self->_dispatch_types } ) {
2633d7dc 276 last DESCEND if $type->match( $c, $path );
66e28e3f 277 }
b96f127f 278
22f3a8dd 279 # If not, move the last part path to args
4082e678 280 my $arg = pop(@path);
281 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
282 unshift @args, $arg;
fbcc39ad 283 }
284
e63bdf38 285 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
66d7ad40 286
e63bdf38 287 $c->log->debug( 'Path is "' . $req->match . '"' )
288 if ( $c->debug && $req->match );
e3a13771 289
fbcc39ad 290 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
291 if ( $c->debug && @args );
292}
293
b5ecfcf0 294=head2 $self->get_action( $action, $namespace )
1abd6db7 295
4ab87e27 296returns a named action from a given namespace.
297
1abd6db7 298=cut
299
300sub get_action {
bcd1002b 301 my ( $self, $name, $namespace ) = @_;
79a3189a 302 return unless $name;
3d0d6d21 303
28928de9 304 $namespace = join( "/", grep { length } split '/', $namespace || "" );
99fe1710 305
e63bdf38 306 return $self->_action_hash->{"${namespace}/${name}"};
1abd6db7 307}
308
ac5c933b 309=head2 $self->get_action_by_path( $path );
068c0898 310
ac5c933b 311Returns the named action by its full path.
3d0d6d21 312
068c0898 313=cut
3d0d6d21 314
315sub get_action_by_path {
316 my ( $self, $path ) = @_;
ea0e58d9 317 $path =~ s/^\///;
28928de9 318 $path = "/$path" unless $path =~ /\//;
059c085b 319 $self->_action_hash->{$path};
3d0d6d21 320}
321
b5ecfcf0 322=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 323
324=cut
325
326sub get_actions {
327 my ( $self, $c, $action, $namespace ) = @_;
328 return [] unless $action;
3d0d6d21 329
28928de9 330 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 331
332 my @match = $self->get_containers($namespace);
333
684d10ed 334 return map { $_->get_action($action) } @match;
a9dc674c 335}
336
b5ecfcf0 337=head2 $self->get_containers( $namespace )
cfd04b0c 338
4ab87e27 339Return all the action containers for a given namespace, inclusive
340
cfd04b0c 341=cut
342
343sub get_containers {
344 my ( $self, $namespace ) = @_;
a13e21ab 345 $namespace ||= '';
346 $namespace = '' if $namespace eq '/';
cfd04b0c 347
a13e21ab 348 my @containers;
cfd04b0c 349
7f23827b 350 if ( length $namespace ) {
351 do {
059c085b 352 push @containers, $self->_container_hash->{$namespace};
7f23827b 353 } while ( $namespace =~ s#/[^/]+$## );
354 }
90ce41ba 355
059c085b 356 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 357
e63bdf38 358 #return (split '/', $namespace); # isnt this more clear?
a13e21ab 359 my @parts = split '/', $namespace;
cfd04b0c 360}
361
ea0e58d9 362=head2 $self->uri_for_action($action, \@captures)
363
364Takes a Catalyst::Action object and action parameters and returns a URI
365part such that if $c->req->path were this URI part, this action would be
366dispatched to with $c->req->captures set to the supplied arrayref.
367
368If the action object is not available for external dispatch or the dispatcher
369cannot determine an appropriate URI, this method will return undef.
370
371=cut
372
373sub uri_for_action {
374 my ( $self, $action, $captures) = @_;
375 $captures ||= [];
059c085b 376 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 377 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 378 return( $uri eq '' ? '/' : $uri )
379 if defined($uri);
ea0e58d9 380 }
381 return undef;
382}
383
b5ecfcf0 384=head2 $self->register( $c, $action )
aad72cc9 385
4ab87e27 386Make sure all required dispatch types for this action are loaded, then
387pass the action to our dispatch types so they can register it if required.
388Also, set up the tree with the action containers.
389
aad72cc9 390=cut
391
79a3189a 392sub register {
393 my ( $self, $c, $action ) = @_;
394
059c085b 395 my $registered = $self->_registered_dispatch_types;
694d15f1 396
e63bdf38 397 #my $priv = 0; #seems to be unused
694d15f1 398 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 399 next if $key eq 'Private';
694d15f1 400 my $class = "Catalyst::DispatchType::$key";
401 unless ( $registered->{$class} ) {
5fb67d52 402 #some error checking rethrowing here wouldn't hurt.
068c0898 403 eval { Class::MOP::load_class($class) };
059c085b 404 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 405 $registered->{$class} = 1;
406 }
407 }
408
409 # Pass the action to our dispatch types so they can register it if reqd.
059c085b 410 foreach my $type ( @{ $self->_dispatch_types } ) {
9a6ecf4f 411 $type->register( $c, $action );
694d15f1 412 }
413
79a3189a 414 my $namespace = $action->namespace;
a13e21ab 415 my $name = $action->name;
c7116517 416
ad5e4650 417 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 418
419 # Set the method value
a13e21ab 420 $container->add_action($action);
c7116517 421
059c085b 422 $self->_action_hash->{"$namespace/$name"} = $action;
423 $self->_container_hash->{$namespace} = $container;
15e9b5dd 424}
425
ad5e4650 426sub _find_or_create_action_container {
a13e21ab 427 my ( $self, $namespace ) = @_;
428
059c085b 429 my $tree ||= $self->_tree;
99fe1710 430
a13e21ab 431 return $tree->getNodeValue unless $namespace;
78d760bb 432
a13e21ab 433 my @namespace = split '/', $namespace;
434 return $self->_find_or_create_namespace_node( $tree, @namespace )
435 ->getNodeValue;
8505565b 436}
90ce41ba 437
8505565b 438sub _find_or_create_namespace_node {
a13e21ab 439 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 440
a13e21ab 441 return $parent unless $part;
8505565b 442
a13e21ab 443 my $child =
444 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 445
a13e21ab 446 unless ($child) {
447 my $container = Catalyst::ActionContainer->new($part);
448 $parent->addChild( $child = Tree::Simple->new($container) );
449 }
99fe1710 450
a13e21ab 451 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 452}
453
4ab87e27 454=head2 $self->setup_actions( $class, $context )
455
1abd6db7 456
457=cut
458
459sub setup_actions {
11bd4e3e 460 my ( $self, $c ) = @_;
99fe1710 461
12e28165 462
9e81ba44 463 my @classes =
ad5e4650 464 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
059c085b 465 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 466
49070d25 467 foreach my $comp ( values %{ $c->components } ) {
468 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 469 }
e494bd6b 470
ad5e4650 471 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 472
11bd4e3e 473 return unless $c->debug;
99fe1710 474
684d10ed 475 my $privates = Text::SimpleTable->new(
dbf03873 476 [ 20, 'Private' ],
34d28dfd 477 [ 36, 'Class' ],
dbf03873 478 [ 12, 'Method' ]
684d10ed 479 );
99fe1710 480
87b85407 481 my $has_private = 0;
1abd6db7 482 my $walker = sub {
483 my ( $walker, $parent, $prefix ) = @_;
484 $prefix .= $parent->getNodeValue || '';
485 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 486 my $node = $parent->getNodeValue->actions;
99fe1710 487
78d760bb 488 for my $action ( keys %{$node} ) {
b7aebc12 489 my $action_obj = $node->{$action};
b0bb11ec 490 next
491 if ( ( $action =~ /^_.*/ )
492 && ( !$c->config->{show_internal_actions} ) );
684d10ed 493 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 494 $has_private = 1;
1abd6db7 495 }
99fe1710 496
1abd6db7 497 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
498 };
99fe1710 499
059c085b 500 $walker->( $walker, $self->_tree, '' );
1cf0345b 501 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
502 if $has_private;
99fe1710 503
a9cbd748 504 # List all public actions
059c085b 505 $_->list($c) for @{ $self->_dispatch_types };
1abd6db7 506}
507
ad5e4650 508sub _load_dispatch_types {
9e81ba44 509 my ( $self, @types ) = @_;
510
511 my @loaded;
512
513 # Preload action types
514 for my $type (@types) {
515 my $class =
516 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
0fc2d522 517
068c0898 518 eval { Class::MOP::load_class($class) };
9e81ba44 519 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
520 if $@;
059c085b 521 push @{ $self->_dispatch_types }, $class->new;
9e81ba44 522
523 push @loaded, $class;
524 }
525
a13e21ab 526 return @loaded;
9e81ba44 527}
528
6680c772 529no Moose;
e5ecd5bc 530__PACKAGE__->meta->make_immutable;
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;