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