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