File Coverage

blib/lib/Web/MREST/Entity.pm
Criterion Covered Total %
statement 93 103 90.2
branch 14 22 63.6
condition 3 6 50.0
subroutine 19 20 95.0
pod 8 8 100.0
total 137 159 86.1


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2015-2015, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33             # ------------------------
34             # This package contains methods for dealing with request and response
35             # entities (parts two and four of the FSM as described in the L<Web::MREST>
36             # documentation
37             # ------------------------
38              
39             package Web::MREST::Entity;
40              
41 21     21   8649 use strict;
  21         31  
  21         481  
42 21     21   70 use warnings;
  21         23  
  21         531  
43              
44 21     21   62 use App::CELL qw( $CELL $log $meta $site );
  21         27  
  21         2093  
45 21     21   86 use Data::Dumper;
  21         25  
  21         747  
46 21     21   70 use Try::Tiny;
  21         32  
  21         757  
47 21     21   69 use Web::Machine::FSM::States;
  21         28  
  21         240  
48 21     21   4101 use Web::MREST::Util qw( $JSON );
  21         32  
  21         1440  
49              
50 21     21   88 use parent 'Web::MREST::Resource';
  21         27  
  21         158  
51              
52              
53              
54              
55             =head1 NAME
56              
57             Web::MREST::Entity - Methods for dealing with request, response entities
58              
59              
60              
61              
62             =head1 SYNOPSIS
63              
64             Methods for dealing with request, response entities
65              
66              
67              
68              
69             =head1 METHODS
70              
71              
72             =head2 get_acceptable_content_type_handler
73              
74             The method to use to process the request entity (i.e, the "acceptable content
75             type handler") is set in content_types_accepted. Web::Machine only calls the
76             method on PUT requests and those POST requests for which post_is_create is
77             true. On POST requests where post_is_create is false, we have to call it
78             ourselves, and for that we need a way to get to it.
79              
80             =cut
81              
82             sub get_acceptable_content_type_handler {
83 9     9 1 15 my $self = shift;
84 9         29 Web::Machine::FSM::States::_get_acceptable_content_type_handler( $self, $self->request );
85             }
86              
87              
88             =head2 content_types_provided
89              
90             L<Web::Machine> calls this routine to determine how to generate the response
91             body GET requests. (It is not called for PUT, POST, or DELETE requests.)
92              
93             The return value has the following format:
94              
95             [
96             { 'text/html' => 'method_for_html' },
97             { 'application/json' => 'method_for_json' },
98             { 'other/mime' => 'method_for_other_mime' },
99             ]
100              
101             As you can see, this is a list of tuples. The key is a media type and the
102             value is the name of a method. The first tuple is taken as the default.
103              
104             =cut
105            
106             sub content_types_provided {
107 100     100 1 33824 my $self = shift;
108 100         234 my @caller = caller;
109 100         263 $log->debug( "Entering " . __PACKAGE__ . "::content_types_provided, caller is " . Dumper( \@caller ) );
110              
111             return [
112 100         14561 { 'text/html' => 'mrest_generate_response_html' },
113             { 'application/json' => 'mrest_generate_response_json' },
114             ];
115             }
116              
117              
118             =head2 mrest_generate_response_html
119              
120             Normally, clients will communicate with the server via
121             '_render_response_json', but humans need HTML. This method takes the
122             server's JSON response and wraps it up in a nice package.
123             The return value from this method becomes the response entity.
124              
125             =cut
126              
127             sub mrest_generate_response_html {
128 1     1 1 106 my ( $self ) = @_;
129 1         6 $log->debug( "Entering " . __PACKAGE__ . "::_render_response_html (response generator)" );
130            
131 1         99 my $json = $self->mrest_generate_response_json;
132 1 50       4 return $json if ref( $json ) eq 'SCALAR';
133              
134 1         9 my $msgobj = $CELL->msg(
135             'MREST_RESPONSE_HTML',
136             $site->MREST_APPLICATION_MODULE,
137             $json,
138             );
139 1 50       243 my $entity = $msgobj
140             ? $msgobj->text
141             : '<html><body><h1>Internal Error</h1><p>See Resource.pm->_render_response_html</p></body></html>';
142              
143 1         6 $self->response->header('Content-Type' => 'text/html' );
144 1         25 $self->response->content( $entity );
145              
146 1         8 return $entity;
147             }
148              
149              
150             =head2 content_types_accepted
151              
152             L<Web::Machine> calls this routine to determine how to handle the request
153             body (e.g. in PUT requests).
154              
155             =cut
156            
157             sub content_types_accepted {
158 13     13 1 806 my $self = shift;
159 13         39 my @caller = caller;
160 13         43 $log->debug("Entering " . __PACKAGE__ . "::content_types_accepted, caller is " . Dumper( \@caller ) );
161              
162 13         1859 return [ { 'application/json' => 'mrest_process_request_json' }, ]
163             }
164              
165              
166             =head2 mrest_process_request_json
167              
168             PUT and POST requests may contain a request body. This is the "handler
169             function" where we process those requests.
170              
171             We associate this function with 'application/json' via
172             C<content_types_accepted>.
173              
174             =cut
175              
176             sub mrest_process_request_json {
177 13     13 1 397 my $self = shift;
178 13         37 my @caller = caller;
179 13         37 $log->debug("Entering " . __PACKAGE__ . "::mrest_process_request_json, caller is " . Dumper( \@caller ) );
180              
181             # convert body to JSON
182 13         1662 my ( $from_json, $status );
183             try {
184 13     13   315 my $content = $self->request->content;
185 13 100 66     331 if ( ! defined $content or $content eq '' ) {
186 4         17 $log->debug( "There is no request body, assuming JSON null" );
187 4         329 $content = 'null';
188             }
189 13         69 $log->debug( "Attempting to decode JSON request entity $content" );
190 13         1189 $from_json = $JSON->decode( $content );
191 12         47 $log->debug( "Success" );
192             } catch {
193 1     1   11 $status = \400;
194 1         6 $log->error( "Caught JSON decode error; response code should be " . $$status );
195 1         97 $self->mrest_declare_status( 'code' => $$status, explanation => $_ );
196 13         104 };
197 13 100       1150 return $status if ref( $status ) eq 'SCALAR';
198 12         55 $self->push_onto_context( { 'request_entity' => $from_json } );
199              
200 12         49 return $self->mrest_generate_response;
201             }
202              
203              
204             =head2 mrest_process_request
205              
206             Used to call the request handler manually in cases when L<Web::Machine> does
207             not call it for us.
208              
209             =cut
210              
211             sub mrest_process_request {
212 9     9 1 14 my $self = shift;
213 9         40 $log->debug( "Entering " . __PACKAGE__ . "::mrest_process_request" );
214 9         733 my $handler = $self->get_acceptable_content_type_handler;
215 9 50       863 if ( ref( $handler ) eq 'SCALAR' ) {
216 0         0 $self->mrest_declare_status( code => $$handler,
217             explanation => 'Could not get acceptable content type handler' );
218 0         0 return $CELL->status_not_ok;
219             }
220 9         29 $log->debug( "acceptable request handler is: " . Dumper( $handler ) );
221 9         1135 return $self->$handler;
222             }
223              
224              
225             =head2 mrest_generate_response_json
226              
227             First, run pass 2 of the resource handler, which is expected to return an
228             App::CELL::Status object. Second, push that object onto the context. Third,
229             convert that object into JSON and push the JSON onto the context, too. Return
230             the JSON representation of the App::CELL::Status object - this becomes the
231             HTTP response entity.
232              
233             =cut
234              
235             sub mrest_generate_response_json {
236 34     34 1 3686 my ( $self ) = @_;
237 34         42 my ( $d, %h, $before, $after, $after_utf8 );
238 34         84 my @caller = caller;
239 34         96 $log->debug( "Entering " . __PACKAGE__ . "::mrest_generate_response_json, caller is " .
240             Dumper( \@caller ) );
241              
242             # run the handler
243 34         4625 my $handler = $self->context->{'handler'}; # WWWW
244 34         171 $log->debug( "mrest_generate_response_json: Calling resource handler $handler for pass two" );
245 34         2730 my ( $status, $response_obj, $entity );
246             try {
247 34     34   843 $status = $self->$handler(2);
248 34 50       8702 if ( ( my $reftype = ref( $status ) ) ne 'App::CELL::Status' ) {
249 0         0 die "AAAAHAGGHG! Handler $handler, pass two, returned a ->$reftype<-, " .
250             "which is not an App::CELL::Status object!";
251             }
252 34 50 33     89 if ( $status->not_ok and ! $self->status_declared ) {
253 0         0 $status->{'http_code'} = 500;
254 0         0 $self->mrest_declare_status( $status );
255             }
256 34         306 $response_obj = $status->expurgate;
257 34         4883 $entity = $JSON->encode( $response_obj );
258             } catch {
259 0 0   0   0 if ( ! $self->status_declared ) {
260 0         0 $self->mrest_declare_status( code => 500, explanation => $_ );
261             }
262 0         0 my $code = $self->mrest_declared_status_code;
263 0         0 $code += 0;
264 0         0 $status = \$code;
265 34         259 };
266 34         450 $log->debug( "response generator returned " . Dumper( $status ) );
267 34 50       6603 return $status if ref( $status ) eq 'SCALAR';
268              
269             # for PUT requests, we need a Location header if a new resource was created
270 34 100       105 if ( $self->context->{'method'} eq 'PUT' ) {
271 3         8 my $headers = $self->response->headers;
272 3         20 my $uri_path = $self->context->{'uri_path'};
273 3 100       5 $headers->header( 'Location' => $uri_path ) unless $self->context->{'resource_exists'};
274             }
275              
276             # stage the status object to become the response entity
277             $self->push_onto_context( {
278 34         194 'handler_status' => $status,
279             'response_object' => $response_obj,
280             'response_entity' => $entity,
281             } );
282              
283             # put the entity into the response
284 34         103 $self->response->header('Content-Type' => 'application/json' );
285 34         890 $self->response->content( $entity );
286              
287 34         324 $log->debug( "Response will be: " . $self->response->content );
288              
289 34         3396 return $entity;
290             }
291              
292              
293             =head2 mrest_generate_response
294              
295             This should somehow get the response handler and run it.
296              
297             =cut
298              
299             sub mrest_generate_response {
300 13     13 1 17 my $self = shift;
301 13         32 return $self->mrest_generate_response_json;
302             }
303              
304              
305             1;