bye bye Class::C3. for good.
[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
b5ecfcf0 123=head2 $self->forward( $c, $command [, \@arguments ] )
1abd6db7 124
4ab87e27 125Documented in L<Catalyst>
126
1abd6db7 127=cut
128
129sub forward {
e72f8f51 130 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 131
1abd6db7 132 unless ($command) {
133 $c->log->debug('Nothing to forward to') if $c->debug;
134 return 0;
135 }
99fe1710 136
e72f8f51 137 my @args;
068c0898 138
e72f8f51 139 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
140 @args = @{ pop @extra_params }
141 } else {
142 # this is a copy, it may take some abuse from ->_invoke_as_path if the path had trailing parts
143 @args = @{ $c->request->arguments };
144 }
145
146 my $action;
147
148 # forward to a string path ("/foo/bar/gorch") or action object which stringifies to that
149 $action = $self->_invoke_as_path( $c, "$command", \@args );
99fe1710 150
e72f8f51 151 # forward to a component ( "MyApp::*::Foo" or $c->component("...") - a path or an object)
152 unless ($action) {
153 my $method = @extra_params ? $extra_params[0] : "process";
154 $action = $self->_invoke_as_component( $c, $command, $method );
155 }
99fe1710 156
99fe1710 157
e540158b 158 unless ($action) {
159 my $error =
160 qq/Couldn't forward to command "$command": /
161 . qq/Invalid action or component./;
162 $c->error($error);
163 $c->log->debug($error) if $c->debug;
164 return 0;
165 }
bd7d2e94 166
e540158b 167 #push @$args, @_;
adb53907 168
059c085b 169 no warnings 'recursion';
170
6680c772 171 my $orig_args = $c->request->arguments();
172 $c->request->arguments(\@args);
b8f669f3 173 $action->dispatch( $c );
6680c772 174 $c->request->arguments($orig_args);
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 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
66d7ad40 284
e63bdf38 285 $c->log->debug( 'Path is "' . $req->match . '"' )
286 if ( $c->debug && $req->match );
e3a13771 287
fbcc39ad 288 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
289 if ( $c->debug && @args );
290}
291
b5ecfcf0 292=head2 $self->get_action( $action, $namespace )
1abd6db7 293
4ab87e27 294returns a named action from a given namespace.
295
1abd6db7 296=cut
297
298sub get_action {
bcd1002b 299 my ( $self, $name, $namespace ) = @_;
79a3189a 300 return unless $name;
3d0d6d21 301
28928de9 302 $namespace = join( "/", grep { length } split '/', $namespace || "" );
99fe1710 303
e63bdf38 304 return $self->_action_hash->{"${namespace}/${name}"};
1abd6db7 305}
306
ac5c933b 307=head2 $self->get_action_by_path( $path );
068c0898 308
ac5c933b 309Returns the named action by its full path.
3d0d6d21 310
068c0898 311=cut
3d0d6d21 312
313sub get_action_by_path {
314 my ( $self, $path ) = @_;
ea0e58d9 315 $path =~ s/^\///;
28928de9 316 $path = "/$path" unless $path =~ /\//;
059c085b 317 $self->_action_hash->{$path};
3d0d6d21 318}
319
b5ecfcf0 320=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 321
322=cut
323
324sub get_actions {
325 my ( $self, $c, $action, $namespace ) = @_;
326 return [] unless $action;
3d0d6d21 327
28928de9 328 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 329
330 my @match = $self->get_containers($namespace);
331
684d10ed 332 return map { $_->get_action($action) } @match;
a9dc674c 333}
334
b5ecfcf0 335=head2 $self->get_containers( $namespace )
cfd04b0c 336
4ab87e27 337Return all the action containers for a given namespace, inclusive
338
cfd04b0c 339=cut
340
341sub get_containers {
342 my ( $self, $namespace ) = @_;
a13e21ab 343 $namespace ||= '';
344 $namespace = '' if $namespace eq '/';
cfd04b0c 345
a13e21ab 346 my @containers;
cfd04b0c 347
7f23827b 348 if ( length $namespace ) {
349 do {
059c085b 350 push @containers, $self->_container_hash->{$namespace};
7f23827b 351 } while ( $namespace =~ s#/[^/]+$## );
352 }
90ce41ba 353
059c085b 354 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 355
e63bdf38 356 #return (split '/', $namespace); # isnt this more clear?
a13e21ab 357 my @parts = split '/', $namespace;
cfd04b0c 358}
359
ea0e58d9 360=head2 $self->uri_for_action($action, \@captures)
361
362Takes a Catalyst::Action object and action parameters and returns a URI
363part such that if $c->req->path were this URI part, this action would be
364dispatched to with $c->req->captures set to the supplied arrayref.
365
366If the action object is not available for external dispatch or the dispatcher
367cannot determine an appropriate URI, this method will return undef.
368
369=cut
370
371sub uri_for_action {
372 my ( $self, $action, $captures) = @_;
373 $captures ||= [];
059c085b 374 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 375 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 376 return( $uri eq '' ? '/' : $uri )
377 if defined($uri);
ea0e58d9 378 }
379 return undef;
380}
381
b5ecfcf0 382=head2 $self->register( $c, $action )
aad72cc9 383
4ab87e27 384Make sure all required dispatch types for this action are loaded, then
385pass the action to our dispatch types so they can register it if required.
386Also, set up the tree with the action containers.
387
aad72cc9 388=cut
389
79a3189a 390sub register {
391 my ( $self, $c, $action ) = @_;
392
059c085b 393 my $registered = $self->_registered_dispatch_types;
694d15f1 394
e63bdf38 395 #my $priv = 0; #seems to be unused
694d15f1 396 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 397 next if $key eq 'Private';
694d15f1 398 my $class = "Catalyst::DispatchType::$key";
399 unless ( $registered->{$class} ) {
5fb67d52 400 #some error checking rethrowing here wouldn't hurt.
068c0898 401 eval { Class::MOP::load_class($class) };
059c085b 402 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 403 $registered->{$class} = 1;
404 }
405 }
406
407 # Pass the action to our dispatch types so they can register it if reqd.
059c085b 408 foreach my $type ( @{ $self->_dispatch_types } ) {
9a6ecf4f 409 $type->register( $c, $action );
694d15f1 410 }
411
79a3189a 412 my $namespace = $action->namespace;
a13e21ab 413 my $name = $action->name;
c7116517 414
ad5e4650 415 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 416
417 # Set the method value
a13e21ab 418 $container->add_action($action);
c7116517 419
059c085b 420 $self->_action_hash->{"$namespace/$name"} = $action;
421 $self->_container_hash->{$namespace} = $container;
15e9b5dd 422}
423
ad5e4650 424sub _find_or_create_action_container {
a13e21ab 425 my ( $self, $namespace ) = @_;
426
059c085b 427 my $tree ||= $self->_tree;
99fe1710 428
a13e21ab 429 return $tree->getNodeValue unless $namespace;
78d760bb 430
a13e21ab 431 my @namespace = split '/', $namespace;
432 return $self->_find_or_create_namespace_node( $tree, @namespace )
433 ->getNodeValue;
8505565b 434}
90ce41ba 435
8505565b 436sub _find_or_create_namespace_node {
a13e21ab 437 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 438
a13e21ab 439 return $parent unless $part;
8505565b 440
a13e21ab 441 my $child =
442 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 443
a13e21ab 444 unless ($child) {
445 my $container = Catalyst::ActionContainer->new($part);
446 $parent->addChild( $child = Tree::Simple->new($container) );
447 }
99fe1710 448
a13e21ab 449 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 450}
451
4ab87e27 452=head2 $self->setup_actions( $class, $context )
453
1abd6db7 454
455=cut
456
457sub setup_actions {
11bd4e3e 458 my ( $self, $c ) = @_;
99fe1710 459
12e28165 460
9e81ba44 461 my @classes =
ad5e4650 462 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
059c085b 463 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 464
49070d25 465 foreach my $comp ( values %{ $c->components } ) {
466 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 467 }
e494bd6b 468
ad5e4650 469 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 470
11bd4e3e 471 return unless $c->debug;
99fe1710 472
684d10ed 473 my $privates = Text::SimpleTable->new(
dbf03873 474 [ 20, 'Private' ],
34d28dfd 475 [ 36, 'Class' ],
dbf03873 476 [ 12, 'Method' ]
684d10ed 477 );
99fe1710 478
87b85407 479 my $has_private = 0;
1abd6db7 480 my $walker = sub {
481 my ( $walker, $parent, $prefix ) = @_;
482 $prefix .= $parent->getNodeValue || '';
483 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 484 my $node = $parent->getNodeValue->actions;
99fe1710 485
78d760bb 486 for my $action ( keys %{$node} ) {
b7aebc12 487 my $action_obj = $node->{$action};
b0bb11ec 488 next
489 if ( ( $action =~ /^_.*/ )
490 && ( !$c->config->{show_internal_actions} ) );
684d10ed 491 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 492 $has_private = 1;
1abd6db7 493 }
99fe1710 494
1abd6db7 495 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
496 };
99fe1710 497
059c085b 498 $walker->( $walker, $self->_tree, '' );
1cf0345b 499 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
500 if $has_private;
99fe1710 501
a9cbd748 502 # List all public actions
059c085b 503 $_->list($c) for @{ $self->_dispatch_types };
1abd6db7 504}
505
ad5e4650 506sub _load_dispatch_types {
9e81ba44 507 my ( $self, @types ) = @_;
508
509 my @loaded;
510
511 # Preload action types
512 for my $type (@types) {
513 my $class =
514 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
0fc2d522 515
068c0898 516 eval { Class::MOP::load_class($class) };
9e81ba44 517 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
518 if $@;
059c085b 519 push @{ $self->_dispatch_types }, $class->new;
9e81ba44 520
521 push @loaded, $class;
522 }
523
a13e21ab 524 return @loaded;
9e81ba44 525}
526
6680c772 527no Moose;
e5ecd5bc 528__PACKAGE__->meta->make_immutable;
529
059c085b 530=head2 meta
531
532Provided by Moose
533
1abd6db7 534=head1 AUTHOR
535
536Sebastian Riedel, C<sri@cpan.org>
158c88c0 537Matt S Trout, C<mst@shadowcatsystems.co.uk>
1abd6db7 538
539=head1 COPYRIGHT
540
541This program is free software, you can redistribute it and/or modify it under
542the same terms as Perl itself.
543
544=cut
545
5461;