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   7698 use strict;
  21         49  
  21         611  
42 21     21   107 use warnings;
  21         39  
  21         993  
43              
44 21     21   111 use App::CELL qw( $CELL $log $meta $site );
  21         41  
  21         2433  
45 21     21   136 use Data::Dumper;
  21         46  
  21         857  
46 21     21   115 use Try::Tiny;
  21         44  
  21         997  
47 21     21   151 use Web::Machine::FSM::States;
  21         56  
  21         317  
48 21     21   6119 use Web::MREST::Util qw( $JSON );
  21         45  
  21         1791  
49              
50 21     21   143 use parent 'Web::MREST::Resource';
  21         50  
  21         174  
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 24 my $self = shift;
84 9         44 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 64171 my $self = shift;
108 100         388 my @caller = caller;
109 100         396 $log->debug( "Entering " . __PACKAGE__ . "::content_types_provided, caller is " . Dumper( \@caller ) );
110              
111             return [
112 100         23625 { '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 190 my ( $self ) = @_;
129 1         6 $log->debug( "Entering " . __PACKAGE__ . "::_render_response_html (response generator)" );
130            
131 1         144 my $json = $self->mrest_generate_response_json;
132 1 50       5 return $json if ref( $json ) eq 'SCALAR';
133              
134 1         21 my $msgobj = $CELL->msg(
135             'MREST_RESPONSE_HTML',
136             $site->MREST_APPLICATION_MODULE,
137             $json,
138             );
139 1 50       462 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         8 $self->response->header('Content-Type' => 'text/html' );
144 1         37 $self->response->content( $entity );
145              
146 1         11 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 1548 my $self = shift;
159 13         72 my @caller = caller;
160 13         107 $log->debug("Entering " . __PACKAGE__ . "::content_types_accepted, caller is " . Dumper( \@caller ) );
161              
162 13         2953 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 725 my $self = shift;
178 13         54 my @caller = caller;
179 13         84 $log->debug("Entering " . __PACKAGE__ . "::mrest_process_request_json, caller is " . Dumper( \@caller ) );
180              
181             # convert body to JSON
182 13         2720 my ( $from_json, $status );
183             try {
184 13     13   603 my $content = $self->request->content;
185 13 100 66     692 if ( ! defined $content or $content eq '' ) {
186 4         25 $log->debug( "There is no request body, assuming JSON null" );
187 4         558 $content = 'null';
188             }
189 13         104 $log->debug( "Attempting to decode JSON request entity $content" );
190 13         2140 $from_json = $JSON->decode( $content );
191 12         78 $log->debug( "Success" );
192             } catch {
193 1     1   15 $status = \400;
194 1         8 $log->error( "Caught JSON decode error; response code should be " . $$status );
195 1         135 $self->mrest_declare_status( 'code' => $$status, explanation => $_ );
196 13         163 };
197 13 100       2058 return $status if ref( $status ) eq 'SCALAR';
198 12         79 $self->push_onto_context( { 'request_entity' => $from_json } );
199              
200 12         87 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 23 my $self = shift;
213 9         64 $log->debug( "Entering " . __PACKAGE__ . "::mrest_process_request" );
214 9         1378 my $handler = $self->get_acceptable_content_type_handler;
215 9 50       1482 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         43 $log->debug( "acceptable request handler is: " . Dumper( $handler ) );
221 9         1851 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 7737 my ( $self ) = @_;
237 34         93 my ( $d, %h, $before, $after, $after_utf8 );
238 34         146 my @caller = caller;
239 34         154 $log->debug( "Entering " . __PACKAGE__ . "::mrest_generate_response_json, caller is " .
240             Dumper( \@caller ) );
241              
242             # run the handler
243 34         7470 my $handler = $self->context->{'handler'}; # WWWW
244 34         305 $log->debug( "mrest_generate_response_json: Calling resource handler $handler for pass two" );
245 34         4957 my ( $status, $response_obj, $entity );
246             try {
247 34     34   1560 $status = $self->$handler(2);
248 34 50       15084 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     152 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         609 $response_obj = $status->expurgate;
257 34         7935 $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         394 };
266 34         673 $log->debug( "response generator returned " . Dumper( $status ) );
267 34 50       9571 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       158 if ( $self->context->{'method'} eq 'PUT' ) {
271 3         17 my $headers = $self->response->headers;
272 3         45 my $uri_path = $self->context->{'uri_path'};
273 3 100       10 $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         329 'handler_status' => $status,
279             'response_object' => $response_obj,
280             'response_entity' => $entity,
281             } );
282              
283             # put the entity into the response
284 34         177 $self->response->header('Content-Type' => 'application/json' );
285 34         1744 $self->response->content( $entity );
286              
287 34         635 $log->debug( "Response will be: " . $self->response->content );
288              
289 34         5828 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 37 my $self = shift;
301 13         72 return $self->mrest_generate_response_json;
302             }
303              
304              
305             1;