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