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