Create branch register_actions.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
1 package Catalyst::Dispatcher;
2
3 use Moose;
4 use Class::MOP;
5 with 'MooseX::Emulate::Class::Accessor::Fast';
6
7 use Catalyst::Exception;
8 use Catalyst::Utils;
9 use Catalyst::Action;
10 use Catalyst::ActionContainer;
11 use Catalyst::DispatchType::Default;
12 use Catalyst::DispatchType::Index;
13 use Catalyst::Utils;
14 use Text::SimpleTable;
15 use Tree::Simple;
16 use Tree::Simple::Visitor::FindByPath;
17
18 # Refactoring note:
19 # do these belong as package vars or should we build these via a builder method?
20 # See Catalyst-Plugin-Server for them being added to, which should be much less ugly.
21
22 # Preload these action types
23 our @PRELOAD = qw/Index Path Regex/;
24
25 # Postload these action types
26 our @POSTLOAD = qw/Default/;
27
28 # Note - see back-compat methods at end of file.
29 has _tree => (is => 'rw');
30 has _dispatch_types => (is => 'rw', default => sub { [] }, required => 1, lazy => 1);
31 has _registered_dispatch_types => (is => 'rw', default => sub { {} }, required => 1, lazy => 1);
32 has _method_action_class => (is => 'rw', default => 'Catalyst::Action');
33 has _action_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
34 has _container_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
35
36 has preload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@PRELOAD] });
37 has postload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@POSTLOAD] });
38
39 # Wrap accessors so you can assign a list and it will capture a list ref.
40 around qw/preload_dispatch_types postload_dispatch_types/ => sub {
41     my $orig = shift;
42     my $self = shift;
43     return $self->$orig([@_]) if (scalar @_ && ref $_[0] ne 'ARRAY');
44     return $self->$orig(@_);
45 };
46
47 no Moose;
48
49 =head1 NAME
50
51 Catalyst::Dispatcher - The Catalyst Dispatcher
52
53 =head1 SYNOPSIS
54
55 See L<Catalyst>.
56
57 =head1 DESCRIPTION
58
59 This is the class that maps public urls to actions in your Catalyst
60 application based on the attributes you set.
61
62 =head1 METHODS
63
64 =head2 new 
65
66 Construct a new dispatcher.
67
68 =cut
69
70 sub BUILD {
71   my ($self, $params) = @_;
72
73   my $container =
74     Catalyst::ActionContainer->new( { part => '/', actions => {} } );
75
76   $self->_tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
77 }
78
79 =head2 $self->preload_dispatch_types
80
81 An arrayref of pre-loaded dispatchtype classes
82
83 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
84 To use a custom class outside the regular C<Catalyst> namespace, prefix
85 it with a C<+>, like so:
86
87     +My::Dispatch::Type
88
89 =head2 $self->postload_dispatch_types
90
91 An arrayref of post-loaded dispatchtype classes
92
93 Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
94 To use a custom class outside the regular C<Catalyst> namespace, prefix
95 it with a C<+>, like so:
96
97     +My::Dispatch::Type
98
99 =head2 $self->dispatch($c)
100
101 Delegate the dispatch to the action that matched the url, or return a
102 message about unknown resource
103
104 =cut
105
106 sub dispatch {
107     my ( $self, $c ) = @_;
108     if ( my $action = $c->action ) {
109         $c->forward( join( '/', '', $action->namespace, '_DISPATCH' ) );
110     }
111     else {
112         my $path  = $c->req->path;
113         my $error = $path
114           ? qq/Unknown resource "$path"/
115           : "No default action defined";
116         $c->log->error($error) if $c->debug;
117         $c->error($error);
118     }
119 }
120
121 # $self->_command2action( $c, $command [, \@arguments ] )
122 # Search for an action, from the command and returns C<($action, $args)> on
123 # success. Returns C<(0)> on error.
124
125 sub _command2action {
126     my ( $self, $c, $command, @extra_params ) = @_;
127
128     unless ($command) {
129         $c->log->debug('Nothing to go to') if $c->debug;
130         return 0;
131     }
132
133     my @args;
134
135     if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
136         @args = @{ pop @extra_params }
137     } else {
138         # this is a copy, it may take some abuse from
139         # ->_invoke_as_path if the path had trailing parts
140         @args = @{ $c->request->arguments };
141     }
142
143     my $action;
144
145     # go to a string path ("/foo/bar/gorch")
146     # or action object
147     if (blessed($command) && $command->isa('Catalyst::Action')) {
148         $action = $command;
149     }
150     else {
151         $action = $self->_invoke_as_path( $c, "$command", \@args );
152     }
153
154     # go to a component ( "MyApp::*::Foo" or $c->component("...")
155     # - a path or an object)
156     unless ($action) {
157         my $method = @extra_params ? $extra_params[0] : "process";
158         $action = $self->_invoke_as_component( $c, $command, $method );
159     }
160
161     return $action, \@args;
162 }
163
164 =head2 $self->visit( $c, $command [, \@arguments ] )
165
166 Documented in L<Catalyst>
167
168 =cut
169
170 sub visit {
171     my $self = shift;
172     $self->_do_visit('visit', @_);
173 }
174
175 sub _do_visit {
176     my $self = shift;
177     my $opname = shift;
178     my ( $c, $command ) = @_;
179     my ( $action, $args ) = $self->_command2action(@_);
180     my $error = qq/Couldn't $opname("$command"): /;
181
182     if (!$action) {
183         $error .= qq/Couldn't $opname to command "$command": /
184                  .qq/Invalid action or component./;
185     }
186     elsif (!defined $action->namespace) {
187         $error .= qq/Action has no namespace: cannot $opname() to a plain /
188                  .qq/method or component, must be a :Action or some sort./
189     }
190     elsif (!$action->class->can('_DISPATCH')) {
191         $error .= qq/Action cannot _DISPATCH. /
192                  .qq/Did you try to $opname() a non-controller action?/;
193     }
194     else {
195         $error = q();
196     }
197
198     if($error) {
199         $c->error($error);
200         $c->log->debug($error) if $c->debug;
201         return 0;
202     }
203
204     $action = $self->expand_action($action);
205
206     local $c->request->{arguments} = $args;
207     local $c->{namespace} = $action->{'namespace'};
208     local $c->{action} = $action;
209
210     $self->dispatch($c);
211 }
212
213 =head2 $self->go( $c, $command [, \@arguments ] )
214
215 Documented in L<Catalyst>
216
217 =cut
218
219 sub go {
220     my $self = shift;
221     $self->_do_visit('go', @_);
222     die $Catalyst::GO;
223 }
224
225 =head2 $self->forward( $c, $command [, \@arguments ] )
226
227 Documented in L<Catalyst>
228
229 =cut
230
231 sub forward {
232     my $self = shift;
233     $self->_do_forward(forward => @_);
234 }
235
236 sub _do_forward {
237     my $self = shift;
238     my $opname = shift;
239     my ( $c, $command ) = @_;
240     my ( $action, $args ) = $self->_command2action(@_);
241
242     if (!$action) {
243         my $error .= qq/Couldn't $opname to command "$command": /
244                     .qq/Invalid action or component./;
245         $c->error($error);
246         $c->log->debug($error) if $c->debug;
247         return 0;
248     }
249
250     no warnings 'recursion';
251
252     local $c->request->{arguments} = $args;
253     $action->dispatch( $c );
254
255     return $c->state;
256 }
257
258 =head2 $self->detach( $c, $command [, \@arguments ] )
259
260 Documented in L<Catalyst>
261
262 =cut
263
264 sub detach {
265     my ( $self, $c, $command, @args ) = @_;
266     $self->_do_forward(detach => $c, $command, @args ) if $command;
267     die $Catalyst::DETACH;
268 }
269
270 sub _action_rel2abs {
271     my ( $self, $c, $path ) = @_;
272
273     unless ( $path =~ m#^/# ) {
274         my $namespace = $c->stack->[-1]->namespace;
275         $path = "$namespace/$path";
276     }
277
278     $path =~ s#^/##;
279     return $path;
280 }
281
282 sub _invoke_as_path {
283     my ( $self, $c, $rel_path, $args ) = @_;
284
285     my $path = $self->_action_rel2abs( $c, $rel_path );
286
287     my ( $tail, @extra_args );
288     while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
289     {                           # allow $path to be empty
290         if ( my $action = $c->get_action( $tail, $path ) ) {
291             push @$args, @extra_args;
292             return $action;
293         }
294         else {
295             return
296               unless $path
297               ; # if a match on the global namespace failed then the whole lookup failed
298         }
299
300         unshift @extra_args, $tail;
301     }
302 }
303
304 sub _find_component_class {
305     my ( $self, $c, $component ) = @_;
306
307     return ref($component)
308       || ref( $c->component($component) )
309       || $c->component($component);
310 }
311
312 sub _invoke_as_component {
313     my ( $self, $c, $component, $method ) = @_;
314
315     my $class = $self->_find_component_class( $c, $component ) || return 0;
316
317     if ( my $code = $class->can($method) ) {
318         return $self->_method_action_class->new(
319             {
320                 name      => $method,
321                 code      => $code,
322                 reverse   => "$class->$method",
323                 class     => $class,
324                 namespace => Catalyst::Utils::class2prefix(
325                     $class, $c->config->{case_sensitive}
326                 ),
327             }
328         );
329     }
330     else {
331         my $error =
332           qq/Couldn't forward to "$class". Does not implement "$method"/;
333         $c->error($error);
334         $c->log->debug($error)
335           if $c->debug;
336         return 0;
337     }
338 }
339
340 =head2 $self->prepare_action($c)
341
342 Find an dispatch type that matches $c->req->path, and set args from it.
343
344 =cut
345
346 sub prepare_action {
347     my ( $self, $c ) = @_;
348     my $req = $c->req;
349     my $path = $req->path;
350     my @path = split /\//, $req->path;
351     $req->args( \my @args );
352
353     unshift( @path, '' );    # Root action
354
355   DESCEND: while (@path) {
356         $path = join '/', @path;
357         $path =~ s#^/##;
358
359         $path = '' if $path eq '/';    # Root action
360
361         # Check out dispatch types to see if any will handle the path at
362         # this level
363
364         foreach my $type ( @{ $self->_dispatch_types } ) {
365             last DESCEND if $type->match( $c, $path );
366         }
367
368         # If not, move the last part path to args
369         my $arg = pop(@path);
370         $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
371         unshift @args, $arg;
372     }
373
374     s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
375
376     $c->log->debug( 'Path is "' . $req->match . '"' )
377       if ( $c->debug && defined $req->match && length $req->match );
378
379     $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
380       if ( $c->debug && @args );
381 }
382
383 =head2 $self->get_action( $action, $namespace )
384
385 returns a named action from a given namespace.
386
387 =cut
388
389 sub get_action {
390     my ( $self, $name, $namespace ) = @_;
391     return unless $name;
392
393     $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
394
395     return $self->_action_hash->{"${namespace}/${name}"};
396 }
397
398 =head2 $self->get_action_by_path( $path ); 
399
400 Returns the named action by its full path. 
401
402 =cut
403
404 sub get_action_by_path {
405     my ( $self, $path ) = @_;
406     $path =~ s/^\///;
407     $path = "/$path" unless $path =~ /\//;
408     $self->_action_hash->{$path};
409 }
410
411 =head2 $self->get_actions( $c, $action, $namespace )
412
413 =cut
414
415 sub get_actions {
416     my ( $self, $c, $action, $namespace ) = @_;
417     return [] unless $action;
418
419     $namespace = join( "/", grep { length } split '/', $namespace || "" );
420
421     my @match = $self->get_containers($namespace);
422
423     return map { $_->get_action($action) } @match;
424 }
425
426 =head2 $self->get_containers( $namespace )
427
428 Return all the action containers for a given namespace, inclusive
429
430 =cut
431
432 sub get_containers {
433     my ( $self, $namespace ) = @_;
434     $namespace ||= '';
435     $namespace = '' if $namespace eq '/';
436
437     my @containers;
438
439     if ( length $namespace ) {
440         do {
441             push @containers, $self->_container_hash->{$namespace};
442         } while ( $namespace =~ s#/[^/]+$## );
443     }
444
445     return reverse grep { defined } @containers, $self->_container_hash->{''};
446
447     #return (split '/', $namespace); # isnt this more clear?
448     my @parts = split '/', $namespace;
449 }
450
451 =head2 $self->uri_for_action($action, \@captures)
452
453 Takes a Catalyst::Action object and action parameters and returns a URI
454 part such that if $c->req->path were this URI part, this action would be
455 dispatched to with $c->req->captures set to the supplied arrayref.
456
457 If the action object is not available for external dispatch or the dispatcher
458 cannot determine an appropriate URI, this method will return undef.
459
460 =cut
461
462 sub uri_for_action {
463     my ( $self, $action, $captures) = @_;
464     $captures ||= [];
465     foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
466         my $uri = $dispatch_type->uri_for_action( $action, $captures );
467         return( $uri eq '' ? '/' : $uri )
468             if defined($uri);
469     }
470     return undef;
471 }
472
473 =head2 expand_action
474
475 expand an action into a full representation of the dispatch.
476 mostly useful for chained, other actions will just return a
477 single action.
478
479 =cut
480
481 sub expand_action {
482     my ($self, $action) = @_;
483
484     foreach my $dispatch_type (@{ $self->_dispatch_types }) {
485         my $expanded = $dispatch_type->expand_action($action);
486         return $expanded if $expanded;
487     }
488
489     return $action;
490 }
491
492 =head2 $self->register( $c, $action )
493
494 Make sure all required dispatch types for this action are loaded, then
495 pass the action to our dispatch types so they can register it if required.
496 Also, set up the tree with the action containers.
497
498 =cut
499
500 sub register {
501     my ( $self, $c, $action ) = @_;
502
503     my $registered = $self->_registered_dispatch_types;
504
505     #my $priv = 0; #seems to be unused
506     foreach my $key ( keys %{ $action->attributes } ) {
507         next if $key eq 'Private';
508         my $class = "Catalyst::DispatchType::$key";
509         unless ( $registered->{$class} ) {
510             # FIXME - Some error checking and re-throwing needed here, as
511             #         we eat exceptions loading dispatch types.
512             eval { Class::MOP::load_class($class) };
513             push( @{ $self->_dispatch_types }, $class->new ) unless $@;
514             $registered->{$class} = 1;
515         }
516     }
517
518     # Pass the action to our dispatch types so they can register it if reqd.
519     foreach my $type ( @{ $self->_dispatch_types } ) {
520         $type->register( $c, $action );
521     }
522
523     my $namespace = $action->namespace;
524     my $name      = $action->name;
525
526     my $container = $self->_find_or_create_action_container($namespace);
527
528     # Set the method value
529     $container->add_action($action);
530
531     $self->_action_hash->{"$namespace/$name"} = $action;
532     $self->_container_hash->{$namespace} = $container;
533 }
534
535 sub _find_or_create_action_container {
536     my ( $self, $namespace ) = @_;
537
538     my $tree ||= $self->_tree;
539
540     return $tree->getNodeValue unless $namespace;
541
542     my @namespace = split '/', $namespace;
543     return $self->_find_or_create_namespace_node( $tree, @namespace )
544       ->getNodeValue;
545 }
546
547 sub _find_or_create_namespace_node {
548     my ( $self, $parent, $part, @namespace ) = @_;
549
550     return $parent unless $part;
551
552     my $child =
553       ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
554
555     unless ($child) {
556         my $container = Catalyst::ActionContainer->new($part);
557         $parent->addChild( $child = Tree::Simple->new($container) );
558     }
559
560     $self->_find_or_create_namespace_node( $child, @namespace );
561 }
562
563 =head2 $self->setup_actions( $class, $context )
564
565 Loads all of the preload dispatch types, registers their actions and then
566 loads all of the postload dispatch types, and iterates over the tree of
567 actions, displaying the debug information if appropriate.
568
569 =cut
570
571 sub setup_actions {
572     my ( $self, $c ) = @_;
573
574     my @classes =
575       $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
576     @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
577
578     foreach my $comp ( values %{ $c->components } ) {
579         $comp->register_actions($c) if $comp->can('register_actions');
580     }
581
582     $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
583
584     return unless $c->debug;
585
586     my $column_width = Catalyst::Utils::term_width() - 20 - 36 - 12;
587     my $privates = Text::SimpleTable->new(
588         [ 20, 'Private' ], [ 36, 'Class' ], [ $column_width, 'Method' ]
589     );
590
591     my $has_private = 0;
592     my $walker = sub {
593         my ( $walker, $parent, $prefix ) = @_;
594         $prefix .= $parent->getNodeValue || '';
595         $prefix .= '/' unless $prefix =~ /\/$/;
596         my $node = $parent->getNodeValue->actions;
597
598         for my $action ( keys %{$node} ) {
599             my $action_obj = $node->{$action};
600             next
601               if ( ( $action =~ /^_.*/ )
602                 && ( !$c->config->{show_internal_actions} ) );
603             $privates->row( "$prefix$action", $action_obj->class, $action );
604             $has_private = 1;
605         }
606
607         $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
608     };
609
610     $walker->( $walker, $self->_tree, '' );
611     $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
612       if $has_private;
613
614     # List all public actions
615     $_->list($c) for @{ $self->_dispatch_types };
616 }
617
618 sub _load_dispatch_types {
619     my ( $self, @types ) = @_;
620
621     my @loaded;
622
623     # Preload action types
624     for my $type (@types) {
625         my $class =
626           ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
627
628         eval { Class::MOP::load_class($class) };
629         Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
630           if $@;
631         push @{ $self->_dispatch_types }, $class->new;
632
633         push @loaded, $class;
634     }
635
636     return @loaded;
637 }
638
639 use Moose;
640
641 # 5.70 backwards compatibility hacks.
642
643 # Various plugins (e.g. Plugin::Server and Plugin::Authorization::ACL)
644 # need the methods here which *should* be private..
645
646 # However we can't really take them away until there is a sane API for
647 # building actions and configuring / introspecting the dispatcher.
648 # In 5.90, we should build that infrastructure, port the plugins which
649 # use it, and then take the crap below away.
650 # See also t/lib/TestApp/Plugin/AddDispatchTypes.pm
651
652 # Alias _method_name to method_name, add a before modifier to warn..
653 foreach my $public_method_name (qw/ 
654         tree 
655         dispatch_types 
656         registered_dispatch_types 
657         method_action_class  
658         action_hash 
659         container_hash
660     /) {
661     my $private_method_name = '_' . $public_method_name;
662     my $meta = __PACKAGE__->meta; # Calling meta method here fine as we happen at compile time.
663     $meta->add_method($public_method_name, $meta->get_method($private_method_name));
664     {
665         my %package_hash; # Only warn once per method, per package. These are infrequent enough that
666                           # I haven't provided a way to disable them, patches welcome.
667         $meta->add_before_method_modifier($public_method_name, sub {
668             my $class = blessed(shift);
669             $package_hash{$class}++ || do { 
670                 warn("Class $class is calling the deprecated method Catalyst::Dispatcher::$public_method_name,\n"
671                     . "this will be removed in Catalyst 5.9X");
672             };
673         });
674     }
675 }
676 # End 5.70 backwards compatibility hacks.
677
678 no Moose;
679 __PACKAGE__->meta->make_immutable;
680
681 =head2 meta
682
683 Provided by Moose
684
685 =head1 AUTHORS
686
687 Catalyst Contributors, see Catalyst.pm
688
689 =head1 COPYRIGHT
690
691 This program is free software, you can redistribute it and/or modify it under
692 the same terms as Perl itself.
693
694 =cut
695
696 1;