Overload to stringify correctly
[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
18use overload '""' => sub { return ref shift }, fallback => 1;
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 ) = @_;
66e28e3f 111 if ( $c->action ) {
28591cd7 112 $c->forward( join( '/', '', $c->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 ) = @_;
257 my $path = $c->req->path;
258 my @path = split /\//, $c->req->path;
259 $c->req->args( \my @args );
260
61a9002d 261 unshift( @path, '' ); # Root action
78d760bb 262
b96f127f 263 DESCEND: while (@path) {
fbcc39ad 264 $path = join '/', @path;
61a9002d 265 $path =~ s#^/##;
fbcc39ad 266
61a9002d 267 $path = '' if $path eq '/'; # Root action
78d760bb 268
22f3a8dd 269 # Check out dispatch types to see if any will handle the path at
270 # this level
271
059c085b 272 foreach my $type ( @{ $self->_dispatch_types } ) {
2633d7dc 273 last DESCEND if $type->match( $c, $path );
66e28e3f 274 }
b96f127f 275
22f3a8dd 276 # If not, move the last part path to args
4082e678 277 my $arg = pop(@path);
278 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
279 unshift @args, $arg;
fbcc39ad 280 }
281
cccc8f68 282 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$c->req->captures||[]};
66d7ad40 283
e3a13771 284 $c->log->debug( 'Path is "' . $c->req->match . '"' )
285 if ( $c->debug && $c->req->match );
286
fbcc39ad 287 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
288 if ( $c->debug && @args );
289}
290
b5ecfcf0 291=head2 $self->get_action( $action, $namespace )
1abd6db7 292
4ab87e27 293returns a named action from a given namespace.
294
1abd6db7 295=cut
296
297sub get_action {
bcd1002b 298 my ( $self, $name, $namespace ) = @_;
79a3189a 299 return unless $name;
3d0d6d21 300
28928de9 301 $namespace = join( "/", grep { length } split '/', $namespace || "" );
99fe1710 302
059c085b 303 return $self->_action_hash->{"$namespace/$name"};
1abd6db7 304}
305
068c0898 306=head2 $self->get_action_by_path( $path );
307
308Returns the named action by its full path.
3d0d6d21 309
068c0898 310=cut
3d0d6d21 311
312sub get_action_by_path {
313 my ( $self, $path ) = @_;
ea0e58d9 314 $path =~ s/^\///;
28928de9 315 $path = "/$path" unless $path =~ /\//;
059c085b 316 $self->_action_hash->{$path};
3d0d6d21 317}
318
b5ecfcf0 319=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 320
321=cut
322
323sub get_actions {
324 my ( $self, $c, $action, $namespace ) = @_;
325 return [] unless $action;
3d0d6d21 326
28928de9 327 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 328
329 my @match = $self->get_containers($namespace);
330
684d10ed 331 return map { $_->get_action($action) } @match;
a9dc674c 332}
333
b5ecfcf0 334=head2 $self->get_containers( $namespace )
cfd04b0c 335
4ab87e27 336Return all the action containers for a given namespace, inclusive
337
cfd04b0c 338=cut
339
340sub get_containers {
341 my ( $self, $namespace ) = @_;
a13e21ab 342 $namespace ||= '';
343 $namespace = '' if $namespace eq '/';
cfd04b0c 344
a13e21ab 345 my @containers;
cfd04b0c 346
7f23827b 347 if ( length $namespace ) {
348 do {
059c085b 349 push @containers, $self->_container_hash->{$namespace};
7f23827b 350 } while ( $namespace =~ s#/[^/]+$## );
351 }
90ce41ba 352
059c085b 353 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 354
a13e21ab 355 my @parts = split '/', $namespace;
cfd04b0c 356}
357
ea0e58d9 358=head2 $self->uri_for_action($action, \@captures)
359
360Takes a Catalyst::Action object and action parameters and returns a URI
361part such that if $c->req->path were this URI part, this action would be
362dispatched to with $c->req->captures set to the supplied arrayref.
363
364If the action object is not available for external dispatch or the dispatcher
365cannot determine an appropriate URI, this method will return undef.
366
367=cut
368
369sub uri_for_action {
370 my ( $self, $action, $captures) = @_;
371 $captures ||= [];
059c085b 372 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 373 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 374 return( $uri eq '' ? '/' : $uri )
375 if defined($uri);
ea0e58d9 376 }
377 return undef;
378}
379
b5ecfcf0 380=head2 $self->register( $c, $action )
aad72cc9 381
4ab87e27 382Make sure all required dispatch types for this action are loaded, then
383pass the action to our dispatch types so they can register it if required.
384Also, set up the tree with the action containers.
385
aad72cc9 386=cut
387
79a3189a 388sub register {
389 my ( $self, $c, $action ) = @_;
390
059c085b 391 my $registered = $self->_registered_dispatch_types;
694d15f1 392
393 my $priv = 0;
394 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 395 next if $key eq 'Private';
694d15f1 396 my $class = "Catalyst::DispatchType::$key";
397 unless ( $registered->{$class} ) {
068c0898 398 #eval "require $class";
399 eval { Class::MOP::load_class($class) };
059c085b 400 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 401 $registered->{$class} = 1;
402 }
403 }
404
405 # Pass the action to our dispatch types so they can register it if reqd.
059c085b 406 foreach my $type ( @{ $self->_dispatch_types } ) {
9a6ecf4f 407 $type->register( $c, $action );
694d15f1 408 }
409
79a3189a 410 my $namespace = $action->namespace;
a13e21ab 411 my $name = $action->name;
c7116517 412
ad5e4650 413 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 414
415 # Set the method value
a13e21ab 416 $container->add_action($action);
c7116517 417
059c085b 418 $self->_action_hash->{"$namespace/$name"} = $action;
419 $self->_container_hash->{$namespace} = $container;
15e9b5dd 420}
421
ad5e4650 422sub _find_or_create_action_container {
a13e21ab 423 my ( $self, $namespace ) = @_;
424
059c085b 425 my $tree ||= $self->_tree;
99fe1710 426
a13e21ab 427 return $tree->getNodeValue unless $namespace;
78d760bb 428
a13e21ab 429 my @namespace = split '/', $namespace;
430 return $self->_find_or_create_namespace_node( $tree, @namespace )
431 ->getNodeValue;
8505565b 432}
90ce41ba 433
8505565b 434sub _find_or_create_namespace_node {
a13e21ab 435 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 436
a13e21ab 437 return $parent unless $part;
8505565b 438
a13e21ab 439 my $child =
440 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 441
a13e21ab 442 unless ($child) {
443 my $container = Catalyst::ActionContainer->new($part);
444 $parent->addChild( $child = Tree::Simple->new($container) );
445 }
99fe1710 446
a13e21ab 447 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 448}
449
4ab87e27 450=head2 $self->setup_actions( $class, $context )
451
1abd6db7 452
453=cut
454
455sub setup_actions {
11bd4e3e 456 my ( $self, $c ) = @_;
99fe1710 457
059c085b 458 $self->_dispatch_types( [] );
459 $self->_registered_dispatch_types( {} );
460 $self->_method_action_class('Catalyst::Action');
461 $self->_action_container_class('Catalyst::ActionContainer');
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}";
068c0898 517 #eval "require $class";
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
059c085b 529=head2 meta
530
531Provided by Moose
532
1abd6db7 533=head1 AUTHOR
534
535Sebastian Riedel, C<sri@cpan.org>
158c88c0 536Matt S Trout, C<mst@shadowcatsystems.co.uk>
1abd6db7 537
538=head1 COPYRIGHT
539
540This program is free software, you can redistribute it and/or modify it under
541the same terms as Perl itself.
542
543=cut
544
5451;