Moving app lookup to _insert_self_into
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Action / SerializeBase.pm
1 #
2 # Catlyst::Action::SerializeBase.pm
3 # Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
4 #
5 # $Id$
6
7 package Catalyst::Action::SerializeBase;
8
9 use strict;
10 use warnings;
11
12 use base 'Catalyst::Action';
13 use Module::Pluggable::Object;
14 use Data::Dump qw(dump);
15 use Catalyst::Request::REST;
16 use Catalyst::Utils ();
17
18 sub new {
19   my $class  = shift;
20   my $config = shift;
21   Catalyst::Request::REST->_insert_self_into( $config->{class} );
22   return $class->SUPER::new($config, @_);
23 }
24
25 __PACKAGE__->mk_accessors(qw(_serialize_plugins _loaded_plugins));
26
27 sub _load_content_plugins {
28     my $self = shift;
29     my ( $search_path, $controller, $c ) = @_;
30
31     unless ( defined( $self->_loaded_plugins ) ) {
32         $self->_loaded_plugins( {} );
33     }
34
35     # Load the Serialize Classes
36     unless ( defined( $self->_serialize_plugins ) ) {
37         my @plugins;
38         my $mpo =
39           Module::Pluggable::Object->new( 'search_path' => [$search_path], );
40         @plugins = $mpo->plugins;
41         $self->_serialize_plugins( \@plugins );
42     }
43
44     # Finally, we load the class.  If you have a default serializer,
45     # and we still don't have a content-type that exists in the map,
46     # we'll use it.
47     my $sclass = $search_path . "::";
48     my $sarg;
49     my $map;
50
51     my $config;
52     
53     if ( exists $controller->{'serialize'} ) {
54         $c->log->info("Using deprecated configuration for Catalyst::Action::REST!");
55         $c->log->info("Please see perldoc Catalyst::Action::REST for the update guide");
56         $config = $controller->{'serialize'};
57     } else {
58         $config = $controller;
59     }
60     $map = $config->{'map'};
61
62     # pick preferred content type
63     my @accepted_types; # priority order, best first
64     # give top priority to content type specified by stash, if any
65     my $content_type_stash_key = $config->{content_type_stash_key};
66     if ($content_type_stash_key
67         and my $stashed = $c->stash->{$content_type_stash_key}
68     ) {
69         # convert to array if not already a ref
70         $stashed = [ $stashed ] if not ref $stashed;
71         push @accepted_types, @$stashed;
72     }
73     # then content types requested by caller
74     push @accepted_types, @{ $c->request->accepted_content_types };
75     # then the default
76     push @accepted_types, $config->{'default'} if $config->{'default'};
77     # pick the best match that we have a serializer mapping for
78     my ($content_type) = grep { $map->{$_} } @accepted_types;
79
80     return $self->_unsupported_media_type($c, $content_type)
81         if not $content_type;
82
83     # carp about old text/x-json
84     if ($content_type eq 'text/x-json') {
85         $c->log->info('Using deprecated text/x-json content-type.');
86         $c->log->info('Use application/json instead!');
87     }
88
89     if ( exists( $map->{$content_type} ) ) {
90         my $mc;
91         if ( ref( $map->{$content_type} ) eq "ARRAY" ) {
92             $mc   = $map->{$content_type}->[0];
93             $sarg = $map->{$content_type}->[1];
94         } else {
95             $mc = $map->{$content_type};
96         }
97         # TODO: Handle custom serializers more elegantly.. this is a start,
98         # but how do we determine which is Serialize and Deserialize?
99         #if ($mc =~ /^+/) {
100         #    $sclass = $mc;
101         #    $sclass =~ s/^+//g;
102         #} else {
103         $sclass .= $mc;
104         #}
105         if ( !grep( /^$sclass$/, @{ $self->_serialize_plugins } ) ) {
106             return $self->_unsupported_media_type($c, $content_type);
107         }
108     } else {
109         return $self->_unsupported_media_type($c, $content_type);
110     }
111     unless ( exists( $self->_loaded_plugins->{$sclass} ) ) {
112         my $load_class = $sclass;
113         $load_class =~ s/::/\//g;
114         $load_class =~ s/$/.pm/g;
115         eval { require $load_class; };
116         if ($@) {
117             $c->log->error(
118                 "Error loading $sclass for " . $content_type . ": $!" );
119             return $self->_unsupported_media_type($c, $content_type);
120         } else {
121             $self->_loaded_plugins->{$sclass} = 1;
122         }
123     }
124
125     if ($search_path eq "Catalyst::Action::Serialize") {
126         if ($content_type) {
127             $c->response->header( 'Vary' => 'Content-Type' );
128         } elsif ($c->request->accept_only) {
129             $c->response->header( 'Vary' => 'Accept' );
130         }
131         $c->response->content_type($content_type);
132     }
133
134     return $sclass, $sarg, $content_type;
135 }
136
137 sub _unsupported_media_type {
138     my ( $self, $c, $content_type ) = @_;
139     $c->res->content_type('text/plain');
140     $c->res->status(415);
141     if (defined($content_type) && $content_type ne "") {
142         $c->res->body(
143             "Content-Type " . $content_type . " is not supported.\r\n" );
144     } else {
145         $c->res->body(
146             "Cannot find a Content-Type supported by your client.\r\n" );
147     }
148     return undef;
149 }
150
151 sub _serialize_bad_request {
152     my ( $self, $c, $content_type, $error ) = @_;
153     $c->res->content_type('text/plain');
154     $c->res->status(400);
155     $c->res->body(
156         "Content-Type " . $content_type . " had a problem with your request.\r\n***ERROR***\r\n$error" );
157     return undef;
158 }
159
160 1;
161
162 =head1 NAME
163
164 B<Catalyst::Action::SerializeBase>
165
166 Base class for Catalyst::Action::Serialize and Catlayst::Action::Deserialize.
167
168 =head1 DESCRIPTION
169
170 This module implements the plugin loading and content-type negotiating
171 code for L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>.
172
173 =head1 SEE ALSO
174
175 L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>,
176 L<Catalyst::Controller::REST>,
177
178 =head1 AUTHOR
179
180 Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway.
181
182 Marchex, Inc. paid me while I developed this module.  (http://www.marchex.com)
183
184 =head1 LICENSE
185
186 You may distribute this code under the same terms as Perl itself.
187
188 =cut
189