Refactor ->forward in the Dispatcher so that it isn't a Damian function
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
3use strict;
fbcc39ad 4use base 'Class::Accessor::Fast';
a2f2cde9 5use Catalyst::Exception;
f05af9ba 6use Catalyst::Utils;
fbcc39ad 7use Catalyst::Action;
b7aebc12 8use Catalyst::ActionContainer;
b96f127f 9use Catalyst::DispatchType::Default;
bcccee4e 10use Catalyst::DispatchType::Index;
87b85407 11use Text::SimpleTable;
1abd6db7 12use Tree::Simple;
13use Tree::Simple::Visitor::FindByPath;
14
fbcc39ad 15# Stringify to class
16use overload '""' => sub { return ref shift }, fallback => 1;
17
49070d25 18__PACKAGE__->mk_accessors(
19 qw/tree dispatch_types registered_dispatch_types
e7bb8d33 20 method_action_class action_container_class
21 preload_dispatch_types postload_dispatch_types
a13e21ab 22 action_hash container_hash
9e81ba44 23 /
49070d25 24);
6d030e6f 25
26# Preload these action types
61a9002d 27our @PRELOAD = qw/Index Path Regex/;
1abd6db7 28
2d1d8f91 29# Postload these action types
61a9002d 30our @POSTLOAD = qw/Default/;
2d1d8f91 31
1abd6db7 32=head1 NAME
33
9c053379 34Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 35
36=head1 SYNOPSIS
37
38See L<Catalyst>.
39
40=head1 DESCRIPTION
41
4ab87e27 42This is the class that maps public urls to actions in your Catalyst
43application based on the attributes you set.
44
1abd6db7 45=head1 METHODS
46
76ddf86b 47=head2 new
4ab87e27 48
49Construct a new dispatcher.
50
e7bb8d33 51=cut
52
53sub new {
9e81ba44 54 my $self = shift;
e7bb8d33 55 my $class = ref($self) || $self;
9e81ba44 56
57 my $obj = $class->SUPER::new(@_);
58
e7bb8d33 59 # set the default pre- and and postloads
9e81ba44 60 $obj->preload_dispatch_types( \@PRELOAD );
e7bb8d33 61 $obj->postload_dispatch_types( \@POSTLOAD );
a13e21ab 62 $obj->action_hash( {} );
63 $obj->container_hash( {} );
64
65 # Create the root node of the tree
66 my $container =
67 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
68 $obj->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
69
9e81ba44 70 return $obj;
e7bb8d33 71}
72
73=head2 $self->preload_dispatch_types
74
75An arrayref of pre-loaded dispatchtype classes
76
77Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
78To use a custom class outside the regular C<Catalyst> namespace, prefix
79it with a C<+>, like so:
80
81 +My::Dispatch::Type
82
83=head2 $self->postload_dispatch_types
84
85An arrayref of post-loaded dispatchtype classes
86
87Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
88To use a custom class outside the regular C<Catalyst> namespace, prefix
89it with a C<+>, like so:
90
91 +My::Dispatch::Type
92
b5ecfcf0 93=head2 $self->detach( $c, $command [, \@arguments ] )
6ef62eb2 94
4ab87e27 95Documented in L<Catalyst>
96
6ef62eb2 97=cut
98
99sub detach {
fbcc39ad 100 my ( $self, $c, $command, @args ) = @_;
bd7d2e94 101 $c->forward( $command, @args ) if $command;
fbcc39ad 102 die $Catalyst::DETACH;
6ef62eb2 103}
104
b5ecfcf0 105=head2 $self->dispatch($c)
1abd6db7 106
4ab87e27 107Delegate the dispatch to the action that matched the url, or return a
108message about unknown resource
109
110
1abd6db7 111=cut
112
113sub dispatch {
fbcc39ad 114 my ( $self, $c ) = @_;
66e28e3f 115 if ( $c->action ) {
28591cd7 116 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
fbcc39ad 117 }
118
119 else {
1abd6db7 120 my $path = $c->req->path;
121 my $error = $path
122 ? qq/Unknown resource "$path"/
123 : "No default action defined";
124 $c->log->error($error) if $c->debug;
125 $c->error($error);
126 }
127}
128
b5ecfcf0 129=head2 $self->forward( $c, $command [, \@arguments ] )
1abd6db7 130
4ab87e27 131Documented in L<Catalyst>
132
1abd6db7 133=cut
134
adb53907 135
1abd6db7 136sub forward {
adb53907 137 my ( $self, $c, $command ) = splice( @_, 0, 3 );
99fe1710 138
1abd6db7 139 unless ($command) {
140 $c->log->debug('Nothing to forward to') if $c->debug;
141 return 0;
142 }
99fe1710 143
adb53907 144 my $args = [ @{ $c->request->arguments } ];
99fe1710 145
adb53907 146 @$args = @{ pop @_ } if ( ref( $_[-1] ) eq 'ARRAY' );
99fe1710 147
adb53907 148 my $action = $self->_invoke_as_path( $c, $command, $args )
149 || $self->_invoke_as_component( $c, $command, shift );
99fe1710 150
adb53907 151 unless ( $action ) {
152 my $error = qq/Couldn't forward to command "$command": / . qq/Invalid action or component./;
153 $c->error($error);
154 $c->log->debug($error) if $c->debug;
155 return 0;
156 }
bd7d2e94 157
adb53907 158 #push @$args, @_;
159
160 local $c->request->{arguments} = $args;
161 $action->execute($c);
99fe1710 162
1abd6db7 163 return $c->state;
164}
165
adb53907 166sub _action_rel2abs {
167 my ( $self, $c, $path ) = @_;
168
169 unless ( $path =~ m#^/# ) {
170 my $namespace = $c->stack->[-1]->namespace;
171 $path = "$namespace/$path";
172 }
173
174 $path =~ s#^/##;
175 return $path;
176}
177
178sub _invoke_as_path {
179 my ( $self, $c, $rel_path, $args ) = @_;
180
181 return if ref $rel_path; # it must be a string
182
183 my $path = $self->_action_rel2abs( $c, $rel_path );
184
185 my ($tail, @extra_args);
186 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) ) { # allow $path to be empty
187 if ( my $action = $c->get_action( $tail, $path ) ) {
188 push @$args, @extra_args;
189 return $action;
190 } else {
191 return unless $path; # if a match on the global namespace failed then the whole lookup failed
192 }
193
194 unshift @extra_args, $tail;
195 }
196}
197
198sub _find_component_class {
199 my ( $self, $c, $component ) = @_;
200
201 return ref($component)
202 || ref( $c->component($component) )
203 || $c->component($component)
204}
205
206sub _invoke_as_component {
207 my ( $self, $c, $component, $method ) = @_;
208
209 my $class = $self->_find_component_class( $c, $component ) || return 0;
210 $method ||= "process";
211
212 if ( my $code = $class->can($method) ) {
213 return $self->method_action_class->new(
214 {
215 name => $method,
216 code => $code,
217 reverse => "$class->$method",
218 class => $class,
219 namespace => Catalyst::Utils::class2prefix(
220 $class, $c->config->{case_sensitive}
221 ),
222 }
223 );
224 } else {
225 my $error =
226 qq/Couldn't forward to "$class". Does not implement "$method"/;
227 $c->error($error);
228 $c->log->debug($error)
229 if $c->debug;
230 return 0;
231 }
232}
233
b5ecfcf0 234=head2 $self->prepare_action($c)
fbcc39ad 235
4ab87e27 236Find an dispatch type that matches $c->req->path, and set args from it.
237
fbcc39ad 238=cut
239
240sub prepare_action {
241 my ( $self, $c ) = @_;
242 my $path = $c->req->path;
243 my @path = split /\//, $c->req->path;
244 $c->req->args( \my @args );
245
61a9002d 246 unshift( @path, '' ); # Root action
78d760bb 247
b96f127f 248 DESCEND: while (@path) {
fbcc39ad 249 $path = join '/', @path;
61a9002d 250 $path =~ s#^/##;
fbcc39ad 251
61a9002d 252 $path = '' if $path eq '/'; # Root action
78d760bb 253
22f3a8dd 254 # Check out dispatch types to see if any will handle the path at
255 # this level
256
78d760bb 257 foreach my $type ( @{ $self->dispatch_types } ) {
2633d7dc 258 last DESCEND if $type->match( $c, $path );
66e28e3f 259 }
b96f127f 260
22f3a8dd 261 # If not, move the last part path to args
4082e678 262 my $arg = pop(@path);
263 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
264 unshift @args, $arg;
fbcc39ad 265 }
266
e3a13771 267 $c->log->debug( 'Path is "' . $c->req->match . '"' )
268 if ( $c->debug && $c->req->match );
269
fbcc39ad 270 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
271 if ( $c->debug && @args );
272}
273
b5ecfcf0 274=head2 $self->get_action( $action, $namespace )
1abd6db7 275
4ab87e27 276returns a named action from a given namespace.
277
1abd6db7 278=cut
279
280sub get_action {
bcd1002b 281 my ( $self, $name, $namespace ) = @_;
79a3189a 282 return unless $name;
bcccee4e 283 $namespace ||= '';
772ab8ae 284 $namespace = '' if $namespace eq '/';
99fe1710 285
a13e21ab 286 return $self->action_hash->{"$namespace/$name"};
1abd6db7 287}
288
b5ecfcf0 289=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 290
291=cut
292
293sub get_actions {
294 my ( $self, $c, $action, $namespace ) = @_;
295 return [] unless $action;
296 $namespace ||= '';
297 $namespace = '' if $namespace eq '/';
298
299 my @match = $self->get_containers($namespace);
300
684d10ed 301 return map { $_->get_action($action) } @match;
a9dc674c 302}
303
b5ecfcf0 304=head2 $self->get_containers( $namespace )
cfd04b0c 305
4ab87e27 306Return all the action containers for a given namespace, inclusive
307
cfd04b0c 308=cut
309
310sub get_containers {
311 my ( $self, $namespace ) = @_;
a13e21ab 312 $namespace ||= '';
313 $namespace = '' if $namespace eq '/';
cfd04b0c 314
a13e21ab 315 my @containers;
cfd04b0c 316
a13e21ab 317 do {
318 push @containers, $self->container_hash->{$namespace};
319 } while ( $namespace =~ s#/[^/]+$## );
90ce41ba 320
a13e21ab 321 return reverse grep { defined } @containers, $self->container_hash->{''};
90ce41ba 322
a13e21ab 323 my @parts = split '/', $namespace;
cfd04b0c 324}
325
b5ecfcf0 326=head2 $self->register( $c, $action )
aad72cc9 327
4ab87e27 328Make sure all required dispatch types for this action are loaded, then
329pass the action to our dispatch types so they can register it if required.
330Also, set up the tree with the action containers.
331
aad72cc9 332=cut
333
79a3189a 334sub register {
335 my ( $self, $c, $action ) = @_;
336
694d15f1 337 my $registered = $self->registered_dispatch_types;
338
339 my $priv = 0;
340 foreach my $key ( keys %{ $action->attributes } ) {
341 $priv++ if $key eq 'Private';
342 my $class = "Catalyst::DispatchType::$key";
343 unless ( $registered->{$class} ) {
344 eval "require $class";
345 push( @{ $self->dispatch_types }, $class->new ) unless $@;
346 $registered->{$class} = 1;
347 }
348 }
349
350 # Pass the action to our dispatch types so they can register it if reqd.
01ce0928 351 my $reg = 0;
352 foreach my $type ( @{ $self->dispatch_types } ) {
353 $reg++ if $type->register( $c, $action );
694d15f1 354 }
355
356 return unless $reg + $priv;
357
79a3189a 358 my $namespace = $action->namespace;
a13e21ab 359 my $name = $action->name;
c7116517 360
a13e21ab 361 my $container = $self->find_or_create_action_container($namespace);
15e9b5dd 362
363 # Set the method value
a13e21ab 364 $container->add_action($action);
c7116517 365
a13e21ab 366 $self->action_hash->{"$namespace/$name"} = $action;
367 $self->container_hash->{$namespace} = $container;
15e9b5dd 368}
369
a13e21ab 370sub find_or_create_action_container {
371 my ( $self, $namespace ) = @_;
372
373 my $tree ||= $self->tree;
99fe1710 374
a13e21ab 375 return $tree->getNodeValue unless $namespace;
78d760bb 376
a13e21ab 377 my @namespace = split '/', $namespace;
378 return $self->_find_or_create_namespace_node( $tree, @namespace )
379 ->getNodeValue;
8505565b 380}
90ce41ba 381
8505565b 382sub _find_or_create_namespace_node {
a13e21ab 383 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 384
a13e21ab 385 return $parent unless $part;
8505565b 386
a13e21ab 387 my $child =
388 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 389
a13e21ab 390 unless ($child) {
391 my $container = Catalyst::ActionContainer->new($part);
392 $parent->addChild( $child = Tree::Simple->new($container) );
393 }
99fe1710 394
a13e21ab 395 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 396}
397
4ab87e27 398=head2 $self->setup_actions( $class, $context )
399
1abd6db7 400
401=cut
402
403sub setup_actions {
11bd4e3e 404 my ( $self, $c ) = @_;
99fe1710 405
6d030e6f 406 $self->dispatch_types( [] );
91d4abc5 407 $self->registered_dispatch_types( {} );
49070d25 408 $self->method_action_class('Catalyst::Action');
409 $self->action_container_class('Catalyst::ActionContainer');
12e28165 410
9e81ba44 411 my @classes =
412 $self->do_load_dispatch_types( @{ $self->preload_dispatch_types } );
413 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 414
49070d25 415 foreach my $comp ( values %{ $c->components } ) {
416 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 417 }
e494bd6b 418
9e81ba44 419 $self->do_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 420
11bd4e3e 421 return unless $c->debug;
99fe1710 422
684d10ed 423 my $privates = Text::SimpleTable->new(
dbf03873 424 [ 20, 'Private' ],
425 [ 38, 'Class' ],
426 [ 12, 'Method' ]
684d10ed 427 );
99fe1710 428
87b85407 429 my $has_private = 0;
1abd6db7 430 my $walker = sub {
431 my ( $walker, $parent, $prefix ) = @_;
432 $prefix .= $parent->getNodeValue || '';
433 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 434 my $node = $parent->getNodeValue->actions;
99fe1710 435
78d760bb 436 for my $action ( keys %{$node} ) {
b7aebc12 437 my $action_obj = $node->{$action};
b0bb11ec 438 next
439 if ( ( $action =~ /^_.*/ )
440 && ( !$c->config->{show_internal_actions} ) );
684d10ed 441 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 442 $has_private = 1;
1abd6db7 443 }
99fe1710 444
1abd6db7 445 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
446 };
99fe1710 447
1abd6db7 448 $walker->( $walker, $self->tree, '' );
11bd4e3e 449 $c->log->debug( "Loaded Private actions:\n" . $privates->draw )
49070d25 450 if ($has_private);
99fe1710 451
a9cbd748 452 # List all public actions
11bd4e3e 453 $_->list($c) for @{ $self->dispatch_types };
1abd6db7 454}
455
9e81ba44 456sub do_load_dispatch_types {
457 my ( $self, @types ) = @_;
458
459 my @loaded;
460
461 # Preload action types
462 for my $type (@types) {
463 my $class =
464 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
465 eval "require $class";
466 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
467 if $@;
468 push @{ $self->dispatch_types }, $class->new;
469
470 push @loaded, $class;
471 }
472
a13e21ab 473 return @loaded;
9e81ba44 474}
475
1abd6db7 476=head1 AUTHOR
477
478Sebastian Riedel, C<sri@cpan.org>
158c88c0 479Matt S Trout, C<mst@shadowcatsystems.co.uk>
1abd6db7 480
481=head1 COPYRIGHT
482
483This program is free software, you can redistribute it and/or modify it under
484the same terms as Perl itself.
485
486=cut
487
4881;