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