File: Resource.pm

package info (click to toggle)
libweb-mrest-perl 0.290-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 424 kB
  • sloc: perl: 1,581; makefile: 5; sh: 1
file content (1118 lines) | stat: -rw-r--r-- 34,097 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
# ************************************************************************* 
# Copyright (c) 2014-2016, SUSE LLC
# 
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 
# 3. Neither the name of SUSE LLC nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ************************************************************************* 

# ------------------------
# This package defines how our web server handles the request-response 
# cycle. All the "heavy lifting" is done by Web::Machine and Plack.
# ------------------------

package Web::MREST::Resource;

use strict;
use warnings;
use feature "state";

use App::CELL qw( $CELL $log $meta $site );
use App::CELL::Status;
use Data::Dumper;
use JSON;
use Params::Validate qw( :all );
use Plack::Session;
use Try::Tiny;
use Web::MREST::InitRouter qw( $router );
use Web::MREST::Util qw( $JSON );

# methods/attributes not defined in this module will be inherited from:
use parent 'Web::Machine::Resource';

# use this to muffle debug messages in parts of the FSM
my %muffle = (
    '1' => 0,
    '2' => 1,
    '3' => 1,
    '4' => 1,
    '5' => 0,
);

=head1 NAME

App::MREST::Resource - HTTP request/response cycle




=head1 SYNOPSIS

In C<YourApp/Resource.pm>:

    use parent 'Web::MREST::Resource';

In PSGI file:

    use Web::Machine;

    Web::Machine->new(
        resource => 'App::YourApp::Resource',
    )->to_app;

It is important to understand that the L<Web::Machine> object created is
actually blessed into C<YourApp::Resource>. The line of inheritance is:

    YourApp::Resource 
        -> Web::MREST::Resource 
            -> Web::Machine::Resource
                -> Plack::Component




=head1 DESCRIPTION

Your application should not call any of the routines in this module directly.
They are called by L<Web::Machine> during the course of request processing.
What your application can do is provide its own versions of selected routines.



=head1 METHODS


=head2 Context methods

Methods for manipulating the context, a hash where we accumulate information
about the request.


=head3 context

Constructor/accessor

=cut

sub context {
    my $self = shift;
    $self->{'context'} = shift if @_;
    if ( ! $self->{'context'} ) {
        $self->{'context'} = {};
    }
    return $self->{'context'};
}


=head3 push_onto_context

Takes a hashref and "pushes" it onto C<< $self->{'context'} >> for use later
on in the course of processing the request.

=cut

sub push_onto_context {
    my $self = shift;
    my ( $hr ) = validate_pos( @_, { type => HASHREF } );

    my $context = $self->context;
    foreach my $key ( keys %$hr ) {
        $context->{$key} = $hr->{$key};
    }
    $self->context( $context );
}


=head2 Status declaration methods

Although L<Web::Machine> takes care of setting the HTTP response status code,
but when we have to override L<Web::Machine>'s value we have this "MREST
declared status" mechanism, which places a C<declared_status> property in
the context. During finalization, the HTTP status code placed in this
property overrides the one L<Web::Machine> came up with.


=head3 mrest_declare_status

This method takes either a ready-made L<App::CELL::Status> object or,
alternatively, a PARAMHASH. In the former case, an HTTP status code can be
"forced" on the response by including a C<http_code> property in the
object. In the latter case, the following keys are recognized (and all of
them are optional):

=over

=item level

L<App::CELL::Status> level, can be any of the strings accepted by that module.
Defaults to 'ERR'.

=item code

The HTTP status code to be applied to the response. Include this only if you 
need to override the code set by L<Web::Machine>.

=item explanation

Text explaining the status - use this to comply with RFC2616. Defaults to '<NONE>'.

=item permanent

Boolean value for error statuses, specifies whether or not the error is
permanent - use this to comply with RFC2616. Defaults to true.

=back

=cut

sub mrest_declare_status {
    my $self = shift;
    my @ARGS = @_;
    my @caller = caller;
    $log->debug( "Entering " . __PACKAGE__ . "::mrest_declare_status with argument(s) " .
        Dumper( \@ARGS ) . "\nCaller: " . Dumper( \@caller ) );

    # if status gets declared multiple times, keep only the first one
    if ( exists $self->context->{'declared_status'} ) {
        $log->notice( 
            "Cowardly refusing to overwrite previously declared status with this one: " . 
            Dumper( \@ARGS ) 
        );
        return;
    }

    my $declared_status;

    if ( @ARGS and ref( $ARGS[0] ) eq 'App::CELL::Status' ) {

        #
        # App::CELL::Status object was given; bend it to our needs
        #
        $declared_status = $ARGS[0];

        # make sure there is a payload and it is a hashref
        if ( ! $declared_status->payload ) {
            $declared_status->payload( {} );
        }

        # if 'http_code' property given, move it to the payload
        if ( my $hc = delete( $declared_status->{'http_code'} ) ) {
            $log->debug( "mrest_declare_status: HTTP code is $hc" );
            $declared_status->payload->{'http_code'} = $hc;
        }

        # handle 'permanent' property
        if ( my $pt = delete( $declared_status->{'permanent'} ) ) {
            $declared_status->payload->{'permanent'} = $pt ? JSON::true : JSON::false;
        } else {
            $declared_status->payload->{'permanent'} = JSON::true;
        }

    } else {

        #
        # PARAMHASH was given
        #
        my %ARGS = validate( @ARGS, {
            'level' => { type => SCALAR, default => 'ERR' },
            'code' => { type => SCALAR|UNDEF, default => undef },
            'explanation' => { type => SCALAR, default => '<NONE>' },
            'permanent' => { type => SCALAR, default => 1 },
            'args' => { type => ARRAYREF, optional => 1 },
        } );
        $ARGS{'args'} = [] unless $ARGS{'args'};
        $declared_status = App::CELL::Status->new(
            level => $ARGS{'level'},
            code => $ARGS{'explanation'},
            args => $ARGS{'args'},
            payload => {
                http_code => $ARGS{'code'},  # might be undef
                permanent => ( $ARGS{'permanent'} )
                    ? JSON::true
                    : JSON::false,
            },
        );

    }

    # add standard properties to the payload
    $declared_status->payload->{'uri_path'} = $self->context->{'uri_path'};
    $declared_status->payload->{'resource_name'} = $self->context->{'resource_name'};
    $declared_status->payload->{'http_method'} = $self->context->{'method'};
    $declared_status->payload->{'found_in'} = {
        package => (caller)[0],
        file => (caller)[1],
        line => (caller)[2]+0,
    };

    # the object is "done": push it onto the context
    $self->push_onto_context( {
        'declared_status' => $declared_status,
    } );
}


=head3 mrest_declared_status_code

Accessor method, gets just the HTTP status code (might be undef);
and allows setting the HTTP status code, as well, by providing an argument.

=cut

sub mrest_declared_status_code {
    my ( $self, $arg ) = @_;
    return unless ref( $self->context->{'declared_status'} ) eq 'App::CELL::Status';

    my $dsc = $self->context->{'declared_status'}->payload->{'http_code'};

    if ( $arg ) {
        $log->warn( "Overriding previous declared status code ->" .
            ( $dsc || 'undefined' ) .
            "<- with new value -> " .
            ( $arg || 'undefined' ) .
            "<->" );
        $self->context->{'declared_status'}->payload->{'http_code'} = $arg;
        $dsc = $arg;
    } 

    return $dsc;
}


=head3 mrest_declared_status_explanation

Accessor method, gets just the explanation (might be undef).
Does not allow changing the explanation - for this, nullify the 
declared status and declare a new one.

=cut

sub mrest_declared_status_explanation {
    my ( $self, $arg ) = @_;
    return unless ref( $self->context->{'declared_status'} ) eq 'App::CELL::Status';

    return $self->context->{'declared_status'}->text;
}

=head2 status_declared

Boolean method - checks context for presence of 'declared_status' property. If 
it is present, the value of that property is returned, just as if we had done
C<< $self->context->{'declared_status'} >>. Otherwise, undef (false) is returned.

=cut

sub status_declared {
    my $self = shift;
    if ( my $declared_status_object = $self->context->{'declared_status'} ) {
        #$log->debug( "Declared status: " . Dumper( $declared_status_object ) );
        if ( ref( $declared_status_object ) ne 'App::CELL::Status' ) {
            die "AAAHAAHAAA! Declared status object is not an App::CELL::Status!";
        }
        return $declared_status_object;
    }
    return;
}


=head2 declared_status

Synonym for C<status_declared>

=cut

sub declared_status {
    my $self = shift;
    return $self->status_declared;
}


=head2 nullify_declared_status

This method nullifies any declared status that might be pending.

=cut

sub nullify_declared_status {
    my $self = shift;
    $log->debug( "Nullifying declared status: " . Dumper( $self->context->{'declared_status'} ) );
    delete $self->context->{'declared_status'};
    return;
}


=head2 FSM Part One

The following methods override methods defined by L<Web::Machine::Resource>.
They correspond to what the L<Web::MREST> calls "Part One" of the FSM. To muffle
debug-level log messages from this part of the FSM, set $muffle{1} = 1 (above).


=head3 service_available (B13)

This is the first method called on every incoming request.

=cut

sub service_available {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::service_available (B13)" ) unless $muffle{1};

    $self->init_router unless ref( $router ) and $router->can( 'match' );

    my $path = $self->request->path_info;
    $path =~ s{^\/}{};
    my $reported_path = ( $path eq '' )
        ? 'the root resource'
        : $path;
    $log->info( "Incoming " . $self->request->method . " request for $reported_path" );
    $log->info( "Self is a " . ref( $self ) );
    $self->push_onto_context( { 
        'headers' => $self->request->headers,
        'request' => $self->request,
        'uri_path' => $path,
        'method' => $self->request->method,
    } );
    return $self->mrest_service_available;
}


=head3 mrest_service_available

Hook. If you overlay this and intend to return false, you should call 
C<< $self->mrest_declare_status >> !!

=cut

sub mrest_service_available {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::mrest_service_available" ) unless $muffle{1};
    return 1;
}


=head3 known_methods (B12)

Returns the value of C<MREST_SUPPORTED_HTTP_METHODS> site parameter

=cut

sub known_methods {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::known_methods (B12)" ) unless $muffle{1};

    my $method = $self->context->{'method'};
    my $known_methods = $site->MREST_SUPPORTED_HTTP_METHODS || [ qw( GET POST PUT DELETE ) ];
    $log->debug( "The known methods are " . Dumper( $known_methods ) ) unless $muffle{1};

    if ( ! grep { $method eq $_; } @$known_methods ) {
        $log->debug( "$method is not among the known methods" ) unless $muffle{1};
        $self->mrest_declare_status( explanation => "The request method $method is not one of the supported methods " . join( ', ', @$known_methods ) );
    }
    return $known_methods;
}


=head3 uri_too_long (B11)

Is the URI too long?

=cut

sub uri_too_long {
    my ( $self, $uri ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::uri_too_long (B11)" ) unless $muffle{1};

    my $max_len = $site->MREST_MAX_LENGTH_URI || 100;
    $max_len += 0;
    if ( length $uri > $max_len ) {
        $self->mrest_declare_status;
        return 1;
    }

    $self->push_onto_context( { 'uri' => $uri } );

    return 0;
}



=head3 allowed_methods (B10)

Determines which HTTP methods we recognize for this resource. We return these
methods in an array. If the requested method is not included in the array,
L<Web::Machine> will return the appropriate HTTP error code.

RFC2616 on 405: "The response MUST include an Allow header containing a list of
valid methods for the requested resource." -> this is handled by Web::Machine,
but be aware that if the methods arrayref returned by allowed_methods does
not include the current request method, allow_methods gets called again.

=cut

sub allowed_methods { 
    my ( $self ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::allowed_methods (B10)" ) unless $muffle{1};

    #
    # Does the URI match a known resource?
    #
    my $path = $self->context->{'uri_path'};
    my $method = uc $self->context->{'method'};
    $log->debug( "allowed_methods: path is $path, method is $method" ) unless $muffle{1};
    if ( my $match = $router->match( $path ) ) {
        # path matches resource, but is it defined for this method?
        #$log->debug( "match object: " . Dumper( $match ) );

        my $resource_name = $match->route->target->{'resource_name'};
        $resource_name = ( defined $resource_name )
            ? $resource_name
            : 'NONE_AAGH!';
        $self->push_onto_context( {
            'match_obj' => $match,
            'resource_name' => $resource_name 
        } );
        $log->info( "allowed_methods: $path matches resource ->$resource_name<-" );

        my ( $def, @allowed_methods ) = $self->_extract_allowed_methods( $match->route->target );
        if ( $def ) {
            # method is allowed for this resource; push various values onto the context for later use
            $self->_stash_resource_info( $match );
            $self->_get_handler( $def );
        } else {
            # method not allowed for this resource
            $self->mrest_declare_status( 'explanation' => "Method not allowed for this resource" );
            return \@allowed_methods;
        }

        if ( $self->status_declared ) {
            # something bad happened
            return [];
        }

        # success 
        return \@allowed_methods;

    }
    # if path does not match, return an empty arrayref, which triggers a 405 status code
    $self->mrest_declare_status( 'code' => 400, 'explanation' => "URI does not match a known resource" );
    return []; 
}


sub _extract_allowed_methods {
    my ( $self, $target ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::_extract_allowed_methods" ) unless $muffle{1};
    #$log->debug( "Target is: " . Dumper( $target ) );

    # ---------------------------------------------------------------
    # FIXME: need to come up with a more reasonable way of doing this
    # ---------------------------------------------------------------
    #
    # The keys of the $route->target hash are the allowed methods plus:
    # - 'resource_name'
    # - 'parent'
    # - 'children'
    # - 'documentation'
    #
    # So, using set theory we can say that the set of allowed methods
    # is equal to the set of $route->target hash keys MINUS the set
    # of keys listed above. (This is fine until someone decides to 
    # add another key to a resource definition and forgets to add it 
    # here as well.)
    #
    # ---------------------------------------------------------------

    my @allowed_methods;
    foreach my $method ( keys %{ $target } ) {
        push( @allowed_methods, $method ) unless $method =~ m/(resource_name)|(parent)|(children)|(documentation)/;
    }
    $log->debug( "Allowed methods are " . join( ' ', @allowed_methods ) ) unless $muffle{1};

    return ( $target->{ $self->context->{'method'} }, @allowed_methods );
}


sub _stash_resource_info {
    my ( $self, $match ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::_stash_resource_info" ) unless $muffle{1};

    # N.B.: $uri is the base URI, not the path
    my $uri = $site->MREST_URI
        ? $site->MREST_URI
        : $self->request->base->as_string;

    my $push_hash = { 
        'mapping' => $match->mapping,       # mapping contains values of ':xyz' parts of path
        'uri_base' => $uri,                      # base URI of the REST server
        'components' => $match->route->components, # resource components
    };
    $self->push_onto_context( $push_hash );
    #$log->debug( "allowed_methods: pushed onto context " . Dumper( $push_hash ) );
}


sub _get_handler {
    my ( $self, $def ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::_get_handler with resource definition: " . Dumper( $def ) ) unless $muffle{1};

    # be idempotent
    if ( my $handler_from_context = $self->context->{'handler'} ) {
        return $handler_from_context;
    }

    my $status = 0;
    my $handler_name;
    if ( $handler_name = $def->{'handler'} ) {
	# $handler_name is the name of a method that will hopefully be callable
	# by doing $self->$handler_name
        $self->push_onto_context( {
            'handler' => $handler_name,
        } );
    } else {
       $status = "No handler defined for this resource+method combination!";
    }
    if ( $status ) {
        $self->mrest_declare_status( 'code' => '500', explanation => $status );
        $log->err( "Leaving _get_handler with status $status" );
    } else {
        $log->info( "Leaving _get_handler (all green) - handler is ->$handler_name<-" );
    }
}
  

=head3 malformed_request (B9)

A true return value from this method aborts the FSM and triggers a "400 Bad
Request" response status.

=cut

sub malformed_request {
    my ( $self ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::malformed_request (B9)" ) unless $muffle{1};

    # we examing the request body on PUT and POST only (FIXME: make this configurable)
    my $method = $self->context->{'method'};
    return 0 unless $method =~ m/^(PUT)|(POST)$/;
    #$log->debug( "Method is $method" );

    # get content-type and content-length
    my $content_type = $self->request->headers->header('Content-Type');
    $content_type = '<NONE>' unless defined( $content_type );
    my $content_length = $self->request->headers->header('Content-Length');
    $content_length = '<NONE>' unless defined( $content_length );
    #$log->debug( "Content-Type: $content_type, Content-Length: $content_length" );

    # no Content-Type and/or no Content-Length, yet request body present ->
    # clearly a violation
    if ( $self->request->content ) {
        if ( $content_type eq '<NONE>' or $content_length eq '<NONE>' ) {
            $self->mrest_declare_status( 
                explanation => 'no Content-Type and/or no Content-Length, yet request body present' 
            );
            return 1;
        }
    }

    $self->push_onto_context( { 'headers' => 
        {
            'content-length' => $content_length,
            'content-type' => $content_type,
        } 
    } );

    return $self->mrest_malformed_request;
}


=head3 mrest_malformed_request

Hook

=cut

sub mrest_malformed_request {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::mrest_malformed_request (B9)" ) unless $muffle{1};
    
    return 0;
}


=head3 is_authorized (B8)

Authentication method - should be implemented in the application.

=cut

sub is_authorized {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::is_authorized (B8)" ) unless $muffle{1};
    return 1;
}


=head3 forbidden (B7)

Authorization method - should be implemented in the application.

=cut

sub forbidden {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::forbidden (B7)" ) unless $muffle{1};
    return 0;
}


=head3 valid_content_headers (B6)

Receives a L<Hash::MultiValue> object containing all the C<Content-*> headers
in the request. Checks these against << $site->MREST_VALID_CONTENT_HEADERS >>,
returns false if the check fails, true if it passes.

=cut

sub valid_content_headers {
    my ( $self, $content_headers ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::valid_content_headers (B6)" ) unless $muffle{1};
    $log->debug( "Content headers: " . join( ', ', keys( %$content_headers ) ) ) unless $muffle{1};

    # get site param 
    my $valid_content_headers = $site->MREST_VALID_CONTENT_HEADERS;
    die "AAAAAHAHAAAAAHGGGG!! \$valid_content_headers is not an array reference!!" 
       unless ref( $valid_content_headers ) eq 'ARRAY';

    # check these content headers against it 
    my $valids = _b6_make_hash( $valid_content_headers );
    foreach my $content_header ( keys( %$content_headers ) ) {
        if ( not exists $valids->{$content_header} ) {
            $self->mrest_declare_status( explanation => 
                "Content header ->$content_header<- not found in MREST_VALID_CONTENT_HEADERS"
            );
            return 0;
        }
    }
    return 1;
}

sub _b6_make_hash {
    my $ar = shift;
    my %h;
    foreach my $chn ( @$ar ) {
        $chn = 'Content-' . $chn unless $chn =~ m/^Content-/;
        $h{ $chn } = '';
    }
    return \%h;
}


=head3 known_content_type (B5)

The assumption for C<PUT> and C<POST> requests is that they might have an
accompanying request entity, the type of which should be declared via a
C<Content-Type> header. If the content type is not recognized by the
application, return false from this method to trigger a "415 Unsupported Media
Type" response.

The basic content-types (major portions only) accepted by the application
should be listed in C<< $site->MREST_SUPPORTED_CONTENT_TYPES >>. Override this
method if that's not good by you.

=cut

sub known_content_type {
    my ( $self, $content_type ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::known_content_type (B5)" ) unless $muffle{1};

    return 1 if not $content_type;

    # if $content_type is a blessed object, deal with that
    my $ct_isa = ref( $content_type );
    if ( $ct_isa ) {
        $log->debug( "\$content_type is a ->$ct_isa<-" ) unless $muffle{1};
        if ( $ct_isa ne 'HTTP::Headers::ActionPack::MediaType' ) {
            $self->mrest_declare_status( code => '500', 
                explanation => "Bad content_type class ->$ct_isa<-" );
            return 0;
        }
        $content_type = $content_type->type; # convert object to string
    }

    $log->debug( "Content type of this request is ->$content_type<-" ) unless $muffle{1};

    # push it onto context
    $self->context->{'content_type'} = $content_type;

    # convert supported content types into a hash for easy lookup
    my %types = map { ( $_ => '' ); } @{ $site->MREST_SUPPORTED_CONTENT_TYPES };
    if ( exists $types{ $content_type } ) {
        $log->info( "$content_type is supported" );
        return 1;
    }
    $self->mrest_declare_status( explanation => "Content type ->$content_type<- is not supported" );
    return 0;
}


=head3 valid_entity_length (B4)

Called by Web::Machine with one argument: the length of the request 
body. Return true or false.

=cut

sub valid_entity_length {
    my ( $self, $body_len ) = @_;
    state $max_len = $site->MREST_MAX_LENGTH_REQUEST_ENTITY;
    $log->debug( "Entering " . __PACKAGE__ . "::valid_entity_length, maximum request entity length is $max_len" ) unless $muffle{1};
    $body_len = $body_len || 0;
    $log->info( "Request body is $body_len bytes long" );
    
    if ( $body_len > $max_len ) {
        $self->mrest_declare_status( explanation => "Request body is $body_len bytes long, which exceeds maximum length set in \$site->MREST_MAX_LENGTH_REQUEST_ENTITY" );
        return 0;
    }
    return 1;
}


=head3 charsets_provided

This method causes L<Web::Machine> to encode the response body (if any) in
UTF-8. 

=cut

sub charsets_provided { 
    return [ qw( UTF-8 ) ]; 
}


#=head3 default_charset
#
#Really use UTF-8 all the time.
#
#=cut
#
#sub default_charset { 'utf8'; }


=head2 FSM Part Two (Content Negotiation)

See L<Web::MREST::Entity>.


=head2 FSM Part Three (Resource Existence)


=head2 resource_exists (G7)

The initial check for resource existence is the URI-to-resource mapping, 
which has already taken place in C<allowed_methods>. Having made it to here,
we know that was successful. 

So, what we do here is call the handler function, which is expected to 
return an L<App::CELL::Status> object. How this status is interpreted is
left up to the application: we pass the status object to the
C<mrest_resource_exists> method, which should return either true or false.

For GET and POST, failure means 404 by default, but can be overrided 
by calling C<mrest_declare_status> from within C<mrest_resource_exists>.

For PUT, success means this is an update operation and failure means insert.

For DELETE, failure means "202 Accepted" - i.e. a request to delete a
resource that doesn't exist is accepted, but nothing actually happens.

=cut 

sub resource_exists {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::resource_exists" );
    #$log->debug( "Context is " . Dumper( $self->context ) );
    
    # no handler is grounds for 500
    if ( not exists $self->context->{'handler'} ) {
        $self->mrest_declare_status( code => '500', 
            explanation => 'AAAAAAAAAAGAHH!!! In resource_exists, no handler/mapping on context' );
        return 0; 
    }

    #
    # run handler (first pass) and push result onto context
    #
    my $handler = $self->context->{'handler'};
    $log->debug( "resource_exists: Calling resource handler $handler for the first time" );
    my $bool;
    try {
        $bool = $self->$handler(1);
    } catch {
        $self->mrest_declare_status( code => 500, explanation => $_ );
        $bool = 0;
    };
    $self->push_onto_context( { 'resource_exists' => $bool } );
    return 1 if $bool;

    # Application thinks the resource doesn't exist. Return value will be
    # 0. For GET and DELETE, this should trigger 404 straightaway: make
    # sure the status is declared so we don't send back a bare response.
    # For POST, the next method will be 'allow_missing_post'.
    # For PUT, it will be ...?...

    if ( not $self->status_declared ) {
        my $method = $self->context->{'method'};
        my $explanation = "Received request for non-existent resource";
        if ( $method eq 'GET' ) {
            # 404 will be assigned by Web::Machine
            $self->mrest_declare_status( 'explanation' => $explanation );
        } elsif ( $method eq 'DELETE' ) {
            # for DELETE, Web::Machine would ordinarily return a 202 so
            # we override that
            $self->mrest_declare_status( 'code' => 404, 'explanation' => $explanation );
        }
    }
    return 0;
}


=head2 allow_missing_post

If the application wishes to allow POST to a non-existent resource, this
method will need to be overrided.

=cut

sub allow_missing_post {
    my ( $self ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::allow_missing_post" );

    # we do not allow POST to a non-existent resource, so we declare 404
    $self->mrest_declare_status( 'code' => 404, explanation => 
        'Detected attempt to POST to non-existent resource' ) unless $self->status_declared;

    return 0;
}


=head2 post_is_create

=cut

sub post_is_create {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::post_is_create" );
    
    return $self->mrest_post_is_create;
}


=head2 mrest_post_is_create

Looks for a 'post_is_create' property in the context and returns
1 or 0, as appropriate.

=cut

sub mrest_post_is_create {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::mrest_post_is_create" );

    my $pic = $self->context->{'post_is_create'};
    if ( ! defined( $pic ) ) {
        $log->error( "post_is_create property is missing; defaults to false" );
        return 0;
    }
    if ( $pic ) {
        $log->info( "post_is_create property is true" );
        return 1;
    }
    $log->info( "post_is_create property is false" );
    return 0;
}


=head2 create_path

=cut

sub create_path {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::create_path" );

    # if there is a declared status, return a dummy value
    return "DUMMY" if $self->status_declared;

    return $self->mrest_create_path;
}


=head2 mrest_create_path

This should always return _something_ (never undef)

=cut

sub mrest_create_path {
    my $self = shift;
    $log->debug( "Entering " . __PACKAGE__ . "::mrest_create_path" );

    my $create_path = $self->context->{'create_path'};
    if ( ! defined( $create_path ) ) {
        $site->mrest_declare_status( code => 500, 
            explanation => "Post is create, but create_path missing in handler status" );
        return 'ERROR';
    }
    $log->debug( "Returning create_path " . Dumper( $create_path ) );
    return $create_path;
}


=head2 create_path_after_handler

This is set to true so we can set C<< $self->context->{'create_path'} >> in the handler.

=cut

sub create_path_after_handler { 1 }



=head2 process_post

This is where we construct responses to POST requests that do not create
a new resource. Since we expect our resource handlers to "do the needful",
all we need to do is call the resource handler for pass two.

The return value should be a Web::Machine/HTTP status code
like, e.g., \200 - this ensures that Web::Machine does not attempt to 
encode the response body, as in our case this would introduce a double-
encoding bug.

=cut

sub process_post {
    my $self = shift;
    $log->debug("Entering " . __PACKAGE__ . "::process_post" );

    # Call the request handler.  This way is bad, because it ignores any
    # 'Accept' header provided in the request by the user agent. However, until
    # Web::Machine is patched we have no other way of knowing the request
    # handler's name so we have to hard-code it like this.
    #$self->_load_request_entity;
    #my $status = $self->mrest_process_request;
    #return $status if ref( $status ) eq 'SCALAR';
    #
    #return \200 if $self->context->{'handler_status'}->ok;
    #
    # if the handler status is not ok, there SHOULD be a declared status
    #return $self->mrest_declared_status_code || \500;

    my $status = $self->mrest_process_request;
    $log->debug( "Handler returned: " . Dumper( $status ) );
    return $status;
}


=head2 delete_resource

This method is called on DELETE requests and is supposed to tell L<Web::Machine>
whether or not the DELETE operation was enacted. In our case, we call the
resource handler (pass two).

=cut

sub delete_resource { 
    my $self = shift;
    $log->debug("Entering " . __PACKAGE__ . "::delete_resource");

    my $status = $self->mrest_generate_response;
    return 0 if ref( $status ) eq 'SCALAR' or $self->context->{'handler_status'}->not_ok;
    return 1;
};



=head2 finish_request

This overrides the Web::Machine method of the same name, and is called just
before the final response is constructed and sent. We use it for adding certain
headers in every response.

=cut

sub finish_request {
    my ( $self, $metadata ) = @_;
    state $http_codes = $site->MREST_HTTP_CODES;

    $log->debug( "Entering " . __PACKAGE__ . "::finish_request with metadata: " . Dumper( $metadata ) );

    if ( ! $site->MREST_CACHE_ENABLED ) {
        #
        # tell folks not to cache
        #
        $self->response->header( 'Cache-Control' => $site->MREST_CACHE_CONTROL_HEADER );
        $self->response->header( 'Pragma' => 'no-cache' );
    }

    #
    # when Web::Machine catches an exception, it sends us the text in the
    # metadata -- in practical terms, this means: if the metadata contains an
    # 'exception' property, something died somewhere
    #
    if ( $metadata->{'exception'} ) {
        my $exception = $metadata->{'exception'};
        $exception =~ s/\n//g;
        $self->mrest_declare_status( code => '500', explanation => $exception );
    }

    #
    # if there is a declared status, we assume that it contains the entire
    # intended response and clobber $self->response->content with it
    #
    if ( $self->status_declared ) {
        my $declared_status = $self->context->{'declared_status'};
        $log->debug( "finish_request: declared status is " . Dumper( $declared_status ) );
        if ( ! $declared_status->payload->{'http_code'} ) {
            $declared_status->payload->{'http_code'} = $self->response->code;
        } else {
            $self->response->code( $declared_status->payload->{'http_code'} );
        }
        my $json = $JSON->encode( $declared_status->expurgate );
        $self->response->content( $json ); 
        $self->response->header( 'content-length' => length( $json ) );
    }

    # The return value is ignored, so any effect of this method must be by
    # modifying the response.
    $log->debug( "Response finalized: " . Dumper( $self->response ) );
    return; 
} 

1;