root/lang/perl/Catalyst-Controller-Atompub/tags/0.3.4/lib/Catalyst/Controller/Atompub/Collection.pm @ 6891

Revision 6891, 30.0 kB (checked in by takemaru, 7 years ago)

lang/perl/Catalyst-Controller-Atompub: 0.3.4 released. fix a bug, see Changes in details

Line 
1package Catalyst::Controller::Atompub::Collection;
2
3use strict;
4use warnings;
5
6use Atompub::DateTime qw( datetime );
7use Atompub::MediaType qw( media_type );
8use Atompub::Util qw( is_acceptable_media_type is_allowed_category );
9use Catalyst::Utils;
10use File::Slurp;
11use HTTP::Status;
12use NEXT;
13use POSIX qw( strftime );
14use Text::CSV;
15use Time::HiRes qw( gettimeofday );
16use URI::Escape;
17use XML::Atom::Entry;
18
19use base qw( Catalyst::Controller::Atompub::Base );
20
21__PACKAGE__->mk_accessors( qw( edited ) );
22
23my %COLLECTION_METHOD = ( GET    => '_list',
24                          HEAD   => '_list',
25                          POST   => '_create' );
26my %RESOURCE_METHOD   = ( GET    => '_read',
27                          HEAD   => '_read',
28                          POST   => '_create',
29                          PUT    => '_update',
30                          DELETE => '_delete' );
31
32sub auto :Private {
33    my ( $self, $c ) = @_;
34    $self->{resources} = [];
35    1;
36}
37
38# access to the collection
39sub default :Private {
40    my ( $self, $c ) = @_;
41    my $method = $COLLECTION_METHOD{ uc $c->req->method };
42    if ( ! $method ) {
43        $c->res->headers->allow('GET, HEAD, POST');
44        return $self->error( $c, RC_METHOD_NOT_ALLOWED );
45    }
46    $self->$method( $c );
47}
48
49sub edit_uri :LocalRegex('^([^-/?&#][^/?&#]*)') {
50    my ( $self, $c ) = @_;
51    my $method = $RESOURCE_METHOD{ uc $c->req->method };
52    if ( ! $method ) {
53        $c->res->headers->allow('GET, HEAD, PUT, DELETE');
54        return $self->error( $c, RC_METHOD_NOT_ALLOWED );
55    }
56    $self->$method( $c );
57}
58
59sub resource {
60    my $self = shift;
61    if ( @_ ) {
62        my $rc = shift;
63        push @{ $self->{resources} }, $rc;
64    }
65    else {
66        my ( $rc ) = grep { $_ } @{ $self->{resources} };
67        return $rc;
68    }
69}
70
71*rc = \&resource;
72
73my @ACCESSORS = ( [ 'collection_resource', 'feed',  'is_collection' ],
74                  [ 'entry_resource',      'entry', 'is_entry'      ],
75                  [ 'media_resource',       undef,  'is_media'      ],
76                  [ 'media_link_entry',    'entry', 'is_entry'      ] );
77
78for my $accessor ( @ACCESSORS ) {
79    no strict 'refs'; ## no critic
80    my ( $method, $type, $is ) = @$accessor;
81    *{$method} = sub {
82        my $self = shift;
83        if ( @_ ) {
84            my $rc = $_[0];
85            $rc->type( media_type( $type ) ) if $type;
86            push @{ $self->{resources} }, $rc;
87        }
88        else {
89            my ( $rc ) = grep { ! $_->type || $_->$is }
90                         grep { $_ }
91                             @{ $self->{resources} };
92            return $rc;
93        }
94    };
95}
96
97sub make_edit_uri {
98    my ( $self, $c, @args ) = @_;
99
100    my $collection_uri = $self->info->get( $c, $self )->href;
101
102    my $basename;
103    if ( my $slug = $c->req->slug ) {
104        my $slug = uri_unescape $slug;
105        $slug =~ s/^\s+//; $slug =~ s/\s+$//; $slug =~ s/[.\s]+/_/;
106        $basename = uri_escape lc $slug;
107    }
108    else {
109        my ( $sec, $usec ) = gettimeofday;
110        $basename
111            = join '-', strftime( '%Y%m%d-%H%M%S', localtime($sec) ), sprintf( '%06d', $usec );
112    }
113
114    my @media_types = map { media_type($_) } ( 'entry', @args );
115
116    my @uris;
117    for my $media_type ( @media_types ) {
118        my $ext  = $media_type->extension || 'bin';
119        my $name = join '.', $basename, $ext;
120        push @uris, join '/', $collection_uri, $name;
121    }
122
123    return wantarray ? @uris : $uris[0];
124}
125
126for my $operation qw( list create read update delete ) {
127    no strict 'refs'; ## no critic
128    *{ "do_$operation" } = sub {
129        my ( $self, $c, @args ) = @_;
130        return $self->error( $c, RC_METHOD_NOT_ALLOWED )
131            unless UNIVERSAL::isa( $self->{handler}{ $operation }, 'CODE' );
132        $self->{handler}{ $operation }( $self, $c, @args );
133    };
134}
135
136sub _list {
137    my ( $self, $c ) = @_;
138
139    my $feed = XML::Atom::Feed->new;
140    my $title = $self->info->get( $c, $self )->title;
141    $feed->title( $title );
142
143    if ( $self->{author} ) {
144        my $author = XML::Atom::Person->new;
145        $self->{author}{$_} and $author->$_( $self->{author}{$_} )
146            for qw( name email uri );
147        $feed->author( $author );
148    }
149
150    $feed->updated( datetime->w3c );
151
152    my $uri = $self->info->get( $c, $self )->href;
153
154    $feed->id( $uri );
155    $feed->self_link( $uri );
156
157    my $rc = Catalyst::Controller::Atompub::Collection::Resource->new;
158    $rc->uri( $uri );
159    $rc->body( $feed );
160    $self->collection_resource( $rc );
161
162    $self->do_list( $c )
163        || return $self->error( $c, RC_INTERNAL_SERVER_ERROR, "Cannot list collection: $uri" );
164
165    if ( ! $c->res->content_type ) {
166        return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content-Type not assigned' )
167            if ! $self->collection_resource || ! $self->collection_resource->type;
168        $c->res->content_type( $self->collection_resource->type );
169    }
170
171    return if length $c->res->body;
172
173    return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content body not found' )
174        if ! $self->collection_resource || ! $self->collection_resource->body;
175
176    $c->res->body( $self->collection_resource->serialize );
177}
178
179sub _create {
180    my ( $self, $c ) = @_;
181
182    my $media_type
183        = media_type( $c->req->content_type ) || media_type('application/octet-stream');
184
185    my $coll = $self->info->get( $c, $self );
186
187    return $self->error( $c, RC_UNSUPPORTED_MEDIA_TYPE, "Unsupported media type: $media_type" )
188        unless is_acceptable_media_type( $coll, $media_type );
189
190    $self->edited( datetime );
191
192    if ( $media_type->is_a('entry') ) {
193        my ( $uri ) = $self->make_edit_uri( $c );
194
195        my $entry = XML::Atom::Entry->new( $c->req->body )
196            || return $self->error( $c, RC_BAD_REQUEST, XML::Atom::Entry->errstr );
197
198        return $self->error( $c, RC_BAD_REQUEST, 'Forbidden category' )
199            unless is_allowed_category( $coll, $entry->category );
200
201        $entry->edited( $self->edited->w3c );
202        $entry->updated( $self->edited->w3c ) unless $entry->updated;
203
204        $entry->id( $uri );
205
206        $entry->edit_link( $uri );
207
208        my $rc = Catalyst::Controller::Atompub::Collection::Resource->new;
209        $rc->edited( $self->edited ); # XXX DEPRECATED
210        $rc->uri( $uri );
211        $rc->body( $entry );
212        $self->entry_resource( $rc );
213    }
214    else {
215        my ( $entry_uri, $media_uri ) = $self->make_edit_uri( $c, $media_type );
216
217        return $self->error( $c, RC_BAD_REQUEST, 'No body' )
218            unless $c->req->body;
219
220        my $media
221            = read_file( $c->req->body, binmode => ':raw', err_mode => 'carp' )
222                || return $self->error( $c, RC_BAD_REQUEST, 'No media resource' );
223
224        my $entry = XML::Atom::Entry->new;
225
226        $entry->edited( $self->edited->w3c );
227        $entry->updated( $self->edited->w3c ) unless $entry->updated;
228
229        my $link = XML::Atom::Link->new;
230        $link->rel('edit-media');
231        $link->href( $media_uri );
232        $entry->add_link( $link );
233
234        $entry->title( uri_unescape $c->req->slug || 'No Title' );
235
236        my $content = XML::Atom::Content->new;
237        $content->src( $media_uri );
238        $content->type( $c->req->content_type );
239        $entry->content( $content );
240
241        $entry->summary(' ');
242
243        my $rc = Catalyst::Controller::Atompub::Collection::Resource->new;
244        $rc->edited( $self->edited ); # XXX DEPRECATED
245        $rc->uri( $media_uri );
246        $rc->body( $media );
247        $rc->type( $media_type );
248        $self->media_resource( $rc );
249
250        $entry->id( $entry_uri );
251
252        $link = XML::Atom::Link->new;
253        $link->rel('edit');
254        $link->href( $entry_uri );
255        $entry->add_link( $link );
256
257        $rc = Catalyst::Controller::Atompub::Collection::Resource->new;
258        $rc->edited( $self->edited ); # XXX DEPRECATED
259        $rc->uri( $entry_uri );
260        $rc->body( $entry );
261        $self->media_link_entry( $rc );
262    }
263
264    $self->do_create( $c )
265        || return $self->error( $c, RC_INTERNAL_SERVER_ERROR,
266                                'Cannot create new resource: ' . $self->info->get( $c, $self )->href  );
267
268    $c->res->status( RC_CREATED );
269
270    if ( ! $c->res->location ) {
271        return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Location not found' )
272            if ! $self->entry_resource || ! $self->entry_resource->uri;
273        $c->res->location( $self->entry_resource->uri );
274    }
275
276    if ( ! defined $c->res->etag || ! $c->res->last_modified ) {
277        my %ret = $self->find_version( $c, $c->res->location );
278        $c->res->etag( $ret{etag} )
279            if ! defined $c->res->etag && defined $ret{etag};
280        $c->res->last_modified( $ret{last_modified} )
281            if ! $c->res->last_modified && $ret{last_modified};
282    }
283
284    if ( ! $c->res->content_type ) {
285        return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content-Type not assigned' )
286            if ! $self->entry_resource || ! $self->entry_resource->type;
287        $c->res->content_type( $self->entry_resource->type );
288    }
289
290    return if length $c->res->body;
291
292    return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content body not found' )
293        if ! $self->entry_resource || ! $self->entry_resource->body;
294
295    $c->res->body( $self->entry_resource->serialize );
296}
297
298sub _read {
299    my ( $self, $c ) = @_;
300
301    return $c->res->status( RC_NOT_MODIFIED ) unless $self->_is_modified( $c );
302
303    my $uri = $c->req->uri->no_query;
304
305    my @accepts = $self->info->get( $c, $self )->accepts;
306    my $media_type = @accepts == 0 ? media_type('entry')
307                   : @accepts == 1 ? media_type( $accepts[0] )
308                   :                 undef;
309
310    my $rc = Catalyst::Controller::Atompub::Collection::Resource->new;
311    $rc->uri( $uri );
312    $rc->type( $media_type );
313    $self->rc( $rc );
314
315    $self->do_read( $c )
316        || return $self->error( $c, RC_INTERNAL_SERVER_ERROR, "Cannot read resource: $uri" );
317
318    if ( ! defined $c->res->etag || ! $c->res->last_modified ) {
319        my %ret = $self->find_version( $c, $uri );
320        $c->res->etag( $ret{etag} )
321            if ! defined $c->res->etag && defined $ret{etag};
322        $c->res->last_modified( $ret{last_modified} )
323            if ! $c->res->last_modified && $ret{last_modified};
324    }
325
326    if ( ! $c->res->content_type ) {
327        $self->rc->type( media_type('entry') )
328            if UNIVERSAL::isa( $self->rc->body, 'XML::Atom::Entry' );
329        return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content-Type not assigned' )
330            if ! $self->rc || ! $self->rc->type;
331        $c->res->content_type( $self->rc->type );
332    }
333
334    return if length $c->res->body;
335
336    return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content body not found' )
337        if ! $self->rc || ! $self->rc->body;
338
339    $c->res->body( $self->rc->serialize );
340}
341
342sub _update {
343    my ( $self, $c ) = @_;
344
345    return $self->error( $c, RC_PRECONDITION_FAILED )
346        if $self->_is_modified( $c );
347
348    my $media_type
349        = media_type( $c->req->content_type ) || media_type('application/octet-stream');
350
351    my $coll = $self->info->get( $c, $self );
352
353    return $self->error( $c, RC_UNSUPPORTED_MEDIA_TYPE, "Unsupported media type: $media_type" )
354        if    ! is_acceptable_media_type( $coll, $media_type )
355           && ! $media_type->is_a('entry');
356              # XXX Entries are okay, this is because Media Link Entries can be PUT
357              #     even to the Media Resource Collection
358
359    $self->edited( datetime );
360
361    my $uri = $c->req->uri->no_query;
362
363    my $content;
364    if ( $media_type->is_a('entry') ) {
365        my $entry = XML::Atom::Entry->new( $c->req->body )
366            || return $self->error( $c, RC_BAD_REQUEST, XML::Atom::Entry->errstr );
367
368        return $self->error( $c, RC_BAD_REQUEST, 'Forbidden category' )
369            unless is_allowed_category( $coll, $entry->category );
370
371        $entry->edited( $self->edited->w3c );
372        $entry->updated( $self->edited->w3c ) unless $entry->updated;
373
374        $entry->id( $uri );
375
376        $entry->edit_link( $uri );
377
378        # XXX check edit-media
379
380        $content = $entry;
381
382        $media_type = media_type('entry');
383    }
384    else {
385        $content
386            = read_file( $c->req->body, binmode => ':raw', err_mode => 'carp' )
387                || return $self->error( $c, RC_BAD_REQUEST, 'No media' );
388    }
389
390    my $rc = Catalyst::Controller::Atompub::Collection::Resource->new;
391    $rc->edited( $self->edited ); # XXX DEPRECATED
392    $rc->uri( $uri );
393    $rc->body( $content );
394    $rc->type( $media_type );
395    $self->rc( $rc );
396
397    $self->do_update( $c )
398        || return $self->error( $c, RC_INTERNAL_SERVER_ERROR, "Cannot update resource: $uri" );
399
400    if ( ! defined $c->res->etag || ! $c->res->last_modified ) {
401        my %ret = $self->find_version( $c, $uri );
402        $c->res->etag( $ret{etag} )
403            if ! defined $c->res->etag && defined $ret{etag};
404        $c->res->last_modified( $ret{last_modified} )
405            if ! $c->res->last_modified && $ret{last_modified};
406    }
407
408    if ( ! $c->res->content_type ) {
409        return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content-Type not assigned' )
410            if ! $self->rc || ! $self->rc->type;
411        $c->res->content_type( $self->rc->type );
412    }
413
414    return if length $c->res->body;
415
416    return $self->error( $c, RC_INTERNAL_SERVER_ERROR, 'Content body not found' )
417        if ! $self->rc || ! $self->rc->body;
418
419    $c->res->body( $self->rc->serialize );
420}
421
422sub _delete {
423    my ( $self, $c ) = @_;
424
425# If-Match nor If-Unmodified-Since header is not required on DELETE
426#    return $self->error( $c, RC_PRECONDITION_FAILED )
427#       if $self->_is_modified( $c );
428
429    my $uri = $c->req->uri->no_query;
430
431    my $rc = Catalyst::Controller::Atompub::Collection::Resource->new;
432    $rc->uri( $uri );
433    $self->rc( $rc );
434
435    $c->res->status( RC_NO_CONTENT );
436
437    $self->do_delete( $c )
438        || return $self->error( $c, RC_INTERNAL_SERVER_ERROR, "Cannot delete resource: $uri" );
439}
440
441sub find_version { }
442
443sub _is_modified {
444    my ( $self, $c ) = @_;
445
446    my $method = $c->req->method;
447
448    my %ret = $self->find_version( $c, $c->req->uri->no_query );
449
450    my $etag          = $ret{etag};
451    my $last_modified = $ret{last_modified};
452
453    return $method eq 'GET' ? 1 : 0
454        if ! defined $etag && ! $last_modified; # if don't check version
455
456    my $match = $method eq 'GET' ? $c->req->if_none_match : $c->req->if_match;
457    $match =~ s/^['"](.+)['"]$/$1/ if $match; #" unquote
458
459    return 1 if defined $etag && ( ! defined $match || $etag ne $match );
460
461    my $since = $method eq 'GET' ? $c->req->if_modified_since : $c->req->if_unmodified_since;
462
463    return 1 if $last_modified && ( ! $since || datetime( $last_modified ) != datetime( $since ) );
464
465    return 0;
466}
467
468sub create_action {
469    my $self = shift;
470    my %args = @_; # namespace, name, reverse, class, attributes, code
471
472    my $attr = lc $args{attributes}{Atompub}[0];
473    my $code =    $args{code};
474
475    my $csv = Text::CSV->new( { allow_whitespace => 1 } );
476    $csv->parse( $attr );
477    for ( $csv->fields ) {
478        $self->{handler}{ $_ } = $code if length $_;
479#        %args = (); # removes 'Loaded Private actions' message in initialization
480    }
481
482    $self->NEXT::create_action( %args );
483}
484
485sub URI::no_query { [ split /[?&]/, shift->canonical ]->[0] }
486
487package Catalyst::Controller::Atompub::Collection::Resource;
488
489use Atompub::MediaType qw( media_type );
490
491use base qw( Class::Accessor::Fast );
492
493__PACKAGE__->mk_accessors( qw( uri type body ) );
494
495sub edited {
496    my $self = shift;
497    if (@_) {
498        $self->{edited} = $_[0];
499    }
500    else {
501        warn '$collection->$resource->edited is DEPRECATED, and see $collection->edited';
502        return $self->{edited};
503    }
504}
505
506sub is_collection {
507    my $self = shift;
508    my $media_type = media_type( $self->type ) || return;
509    return $media_type->is_a('feed');
510}
511
512sub is_entry {
513    my $self = shift;
514    my $media_type = media_type( $self->type ) || return;
515    return $media_type->is_a('entry');
516}
517
518sub is_media {
519    my $self = shift;
520    my $media_type = media_type( $self->type ) || return;
521    return ! $media_type->is_a('application/atom+xml');
522}
523
524sub serialize {
525    my $self = shift;
526    my $body = $self->body;
527    UNIVERSAL::can( $body, 'as_xml' ) ? $body->as_xml : $body;
528}
529
5301;
531__END__
532
533=head1 NAME
534
535Catalyst::Controller::Atompub::Collection
536- A Catalyst controller for the Atom Collection Resources
537
538
539=head1 SYNOPSIS
540
541    # Use the Catalyst helper
542    $ perl script/myatom_create.pl controller MyCollection Atompub::Collection
543
544    # And edit lib/MyAtom/Controller/MyCollection.pm
545    package MyAtom::Controller::MyCollection;
546    use base 'Catalyst::Controller::Atompub::Collection';
547
548    # List resources in a Feed Document, which must be implemented in
549    # the mehtod with "Atompub(list)" attribute
550    sub get_feed :Atompub(list) {
551        my ( $self, $c ) = @_;
552   
553        # Skeleton of the Feed (XML::Atom::Feed) was prepared by
554        # C::C::Atompub
555        my $feed = $self->collection_resource->body;
556   
557        # Retrieve Entries sorted in descending order
558        my $rs = $c->model('DBIC::Entries')
559                   ->search( {}, { order_by => 'edited desc' } );
560   
561        # Add Entries to the Feed
562        while ( my $entry_resource = $rs->next ) {
563            my $entry = XML::Atom::Entry->new( \$entry_resource->xml );
564            $feed->add_entry( $entry );
565        }
566   
567        # Return true on success
568        return 1;
569    }
570   
571    # Create new Entry in the method with "Atompub(create)" attribute
572    sub create_entry :Atompub(create) {
573        my ( $self, $c ) = @_;
574   
575        # URI of the new Entry, which was determined by C::C::Atompub
576        my $uri = $self->entry_resource->uri;
577   
578        # app:edited element, which was assigned by C::C::Atompub,
579        # is coverted into ISO 8601 format like '2007-01-01 00:00:00'
580        my $edited = $self->edited->iso;
581
582        # POSTed Entry (XML::Atom::Entry)
583        my $entry = $self->entry_resource->body;
584   
585        # Create new Entry
586        $c->model('DBIC::Entries')->create( {
587            uri    => $uri,
588            edited => $edited,
589            xml    => $entry->as_xml,
590        } );
591   
592        # Return true on success
593        return 1;
594    }
595   
596    # Search the requested Entry in the method with "Atompub(read)"
597    # attribute
598    sub get_entry :Atompub(read) {
599        my ( $self, $c ) = @_;
600   
601        my $uri = $c->entry_resource->uri;
602   
603        # Retrieve the Entry
604        my $rs = $c->model('DBIC::Entries')->find( { uri => $uri } );
605   
606        # Set the Entry
607        my $entry = XML::Atom::Entry->new( \$rs->xml );
608        $self->entry_resource->body( $entry );
609   
610        # Return true on success
611        return 1;
612    }
613   
614    # Update the requested Entry in the method with "Atompub(update)"
615    # attribute
616    sub update_entry :Atompub(update) {
617        my ( $self, $c ) = @_;
618   
619        my $uri = $c->entry_resource->uri;
620   
621        # app:edited element, which was assigned by C::C::Atompub,
622        # is coverted into ISO 8601 format like '2007-01-01 00:00:00'
623        my $edited = $self->edited->iso;
624
625        # PUTted Entry (XML::Atom::Entry)
626        my $entry = $self->entry_resource->body;
627   
628        # Update the Entry
629        $c->model('DBIC::Entries')->find( { uri => $uri } )
630                                  ->update( {
631                                      uri => $uri,
632                                      edited => $edited,
633                                      xml => $entry->as_xml,
634                                  } );
635   
636        # Return true on success
637        return 1;
638    }
639   
640    # Delete the requested Entry in the method with "Atompub(delete)"
641    # attribute
642    sub delete_entry :Atompub(delete) {
643        my ( $self, $c ) = @_;
644   
645        my $uri = $c->entry_resource->uri;
646   
647        # Delete the Entry
648        $c->model('DBIC::Entries')->find( { uri => $uri } )->delete;
649   
650        # Return true on success
651        return 1;
652    }
653       
654    # Access to http://localhost:3000/mycollection and get Feed Document
655
656
657=head1 DESCRIPTION
658
659Catalyst::Controller::Atompub::Collection provides the following features:
660
661=over 4
662
663=item * Pre-processing requests
664
665L<Catalyst::Controller::Atompub::Collection> pre-processes the HTTP requests.
666All you have to do is just writing CRUD operations in the subroutines
667with I<Atompub> attribute.
668
669=item * Media Resource support
670
671Media Resources (binary data) as well as Entry Resources are supported.
672A Media Link Entry, which has an I<atom:link> element to the newly created Media Resource,
673is given by L<Catalyst::Controller::Atompub::Collection>.
674
675=item * Media type check
676
677L<Catalyst::Controller::Atompub::Collection> checks a media type of
678the POSTed/PUTted resource based on collection configuration.
679
680=item * Category check
681
682L<Catalyst::Controller::Atompub::Collection> checks
683I<atom:category> elements in the POSTed/PUTted Entry Document
684based on collection configuration.
685
686=item * Cache controll and versioning
687
688Cache controll and versioning are enabled just by overriding C<find_version> method,
689which returns I<ETag> and/or I<Last-Modified> header.
690
691=item * Naming resources by I<Slug> header
692
693Resource URIs are determined based on I<Slug> header if exists.
694If the I<Slug> header is "Entry 1", the resource URI will be like:
695
696    http://localhost:3000/mycollection/entry_1.atom
697
698The default naming rules can be changed by overriding C<make_edit_uri> method.
699
700=back
701
702
703=head1 SUBCLASSING
704
705One or more subclasses are required in your Atompub server implementation.
706In the subclasses, methods with the following attributes must be defined.
707
708
709=head2 sub xxx :Atompub(list)
710
711Lists resources in a Feed Document.
712
713This method is expected to add Entries and other elements to a skeleton of the Feed.
714The following accessors can be used.
715
716=over 2
717
718=item - $controller->collection_resource->uri
719
720URI of Collection
721
722=item - $controller->collection_resource->body
723
724Skeleton of Feed (L<XML::Atom::Feed>)
725
726=back
727
728Returns true on success, false otherwise.
729
730
731=head2 sub xxx :Atompub(create)
732
733Creates new resource.
734
735=over 4
736
737=item * In Collections with Entry Resources
738
739The implementation is expected to insert the new Entry to your model, such as L<DBIx::Class>.
740The following accessors can be used.
741
742=over 2
743
744=item - $controller->entry_resource->uri
745
746URI of New Entry
747
748=item - $controller->entry_resource->edited
749
750I<app:edited> element of New Entry
751
752=item - $controller->entry_resource->body
753
754New Entry (L<XML::Atom::Entry>)
755
756=back
757
758
759=item * In Collections with Media Resources
760
761The implementation is expected to insert new Media Link Entry as well as new Media Resource
762to your model, such as L<DBIx::Class>.
763
764The following accessors can be used for the Media Resource.
765
766=over 2
767
768=item - $controller->media_resource->uri
769
770URI of New Media Resource
771
772=item - $controller->media_resource->edited
773
774I<app:edited> element of New Media Resource
775
776=item - $controller->media_resource->type
777
778Media type of New Media Resource
779
780=item - $controller->media_resource->body
781
782New Media Resource (a byte string)
783
784=back
785
786
787The following accessors can be used for Media Link Entry.
788
789=over 2
790
791=item - $controller->media_link_entry->uri
792
793URI of New Media Link Entry
794
795=item - $controller->media_link_entry->edited
796
797I<app:edited> element of New Media Link Entry
798
799=item - $controller->media_link_entry->body
800
801New Media Link Entry (L<XML::Atom::Entry>)
802
803=back
804
805
806=back
807
808Returns true on success, false otherwise.
809
810
811=head2 sub xxx :Atompub(read)
812
813Searchs the requested resource.
814
815=over 4
816
817=item * In Collections with Entry Resources
818
819The implementation is expected to search the Entry,
820which must be stored in C<body> accessor described below.
821The following accessors can be used.
822
823=over 2
824
825=item - $controller->entry_resource->body
826
827Entry (L<XML::Atom::Entry>)
828
829=back
830
831=item * In Collections with Media Resources
832
833The implementation is expected to search Media Resource or Media Link Entry,
834which must be stored in C<body> accessor described below.
835The following accessors can be used for the Media Resource.
836
837=over 2
838
839=item - $controller->media_resource->type
840
841Media type of Media Resource
842
843=item - $controller->media_resource->body
844
845Media Resource (a byte string)
846
847=back
848
849The following accessors can be used for the Media Link Entry.
850
851=over 2
852
853=item - $controller->media_link_entry->body
854
855Media Link Entry (L<XML::Atom::Entry>)
856
857=back
858
859=back
860
861Returns true on success, false otherwise.
862
863
864=head2 sub xxx :Atompub(update)
865
866Updates the requested resource.
867
868
869=over 4
870
871=item * In Collections with Entry Resources
872
873The implementation is expected to update the Entry.
874The following accessors can be used.
875
876=over 2
877
878=item - $controller->entry_resource->uri
879
880URI of Entry
881
882=item - $controller->entry_resource->edited
883
884I<app:edited> element of Entry
885
886=item - $controller->entry_resource->body
887
888Entry (L<XML::Atom::Entry>)
889
890=back
891
892
893=item * In Collections with Media Resources
894
895The implementation is expected to update the Media Resource or the Media Link Entry.
896The following accessors can be used for the Media Resource.
897
898=over 2
899
900=item - $controller->media_resource->uri
901
902URI of Media Resource
903
904=item - $controller->media_resource->edited
905
906I<app:edited> element of Media Resource
907
908=item - $controller->media_resource->type
909
910Media type of Media Resource
911
912=item - $controller->media_resource->body
913
914Media Resource (a byte string)
915
916=back
917
918
919The following accessors can be used for the Media Link Entry.
920
921=over 2
922
923=item - $controller->media_link_entry->uri
924
925URI of Media Link Entry
926
927=item - $controller->media_link_entry->edited
928
929I<app:edited> element of Media Link Entry
930
931=item - $controller->media_link_entry->body
932
933Media Link Entry (L<XML::Atom::Entry>)
934
935=back
936
937=back
938
939Returns true on success, false otherwise.
940
941
942=head2 sub xxx :Atompub(delete)
943
944Deletes the requested resource.
945
946The implementation is expected to delete the resource.
947If the collection contains Media Resources,
948corresponding Media Link Entry must be deleted at once.
949
950Returns true on success, false otherwise.
951
952
953=head1 METHODS
954
955The following methods can be overridden to change the default behaviors.
956
957
958=head2 $controller->find_version( $uri )
959
960By overriding C<find_version> method, cache control and versioning are enabled.
961
962The implementation is expected to return I<ETag> and/or I<Last-Modified> value
963of the requested URI:
964
965    package MyAtom::Controller::MyCollection;
966
967    sub find_version {
968        my ( $self, $c, $uri ) = @_;
969
970        # Retrieve ETag and/or Last-Modified of $uri
971
972        return ( etag => $etag, last_modified => $last_modified );
973    }
974
975When a resource of the URI does not exist, the implementation must return an empty array.
976
977The behavior of Atompub server will be changed in the following manner:
978
979=over 4
980
981=item * On GET request
982
983Status code of 304 (Not Modified) will be returned,
984if the requested resource has not been changed.
985
986=item * On PUT request
987
988Status code of 412 (Precondition Failed) will be returned,
989if the current version of the resource that a client is modifying is not
990the same as the version that the client is basing its modifications on.
991
992=back
993
994
995=head2 $controller->make_edit_uri( $c, [ @args ]);
996
997By default, if the I<Slug> header is "Entry 1", the resource URI will be like:
998
999    http://localhost:3000/mycollection/entry_1.atom
1000
1001This default behavior can be changed by overriding C<find_version> method:
1002
1003    package MyAtom::Controller::MyCollection;
1004
1005    sub make_edit_uri {
1006        my ( $self, $c, @args ) = @_;
1007
1008        my @uris = $self->SUPER::make_edit_uri( $c, @args );
1009
1010        # Modify @uris as you like
1011
1012        return @uris;
1013    }
1014
1015Arguments @args are media types of POSTed resources.
1016
1017This method returns an array of resource URIs;
1018the first element is a URI of the Entry Resource (including Media Link Entry),
1019and the second one is a URI of the Media Resource if exists.
1020
1021
1022=head2 $controller->do_list
1023
1024=head2 $controller->do_create
1025
1026=head2 $controller->do_read
1027
1028=head2 $controller->do_update
1029
1030=head2 $controller->do_delete
1031
1032
1033=head1 ACCESSORS
1034
1035=head2 $controller->resource
1036
1037=head2 $controller->rc
1038
1039An accessor for a resource object except Media Link Entry.
1040
1041
1042=head2 $controller->collection_resource
1043
1044An accessor for a Collection Resource object.
1045
1046
1047=head2 $controller->entry_resource
1048
1049An accessor for an Entry Resource objecgt.
1050
1051
1052=head2 $controller->media_resource
1053
1054An accessor for a Media Resource object.
1055
1056
1057=head2 $controller->media_link_entry
1058
1059An accessor for a Media Link Entry object.
1060
1061
1062=head2 $controller->edited
1063
1064An accessor for a app:edited, which is applied for the POSTed/PUTted Entry Resource.
1065
1066
1067=head1 INTERNAL INTERFACES
1068
1069=head2 $controller->auto
1070
1071=head2 $controller->default
1072
1073=head2 $controller->edit_uri
1074
1075=head2 $controller->_list
1076
1077=head2 $controller->_create
1078
1079=head2 $controller->_read
1080
1081=head2 $controller->_update
1082
1083=head2 $controller->_delete
1084
1085=head2 $controller->_is_modified
1086
1087=head2 $controller->create_action
1088
1089
1090=head1 FEED PAGING
1091
1092This module does not provide paging of Feed Documents.
1093Paging mechanism should be implemented in a method with "Atompub(list)" attribute.
1094
1095
1096=head1 CONFIGURATION
1097
1098By default (no configuration), Collections accept Entry Documents
1099(application/atom+xml) and any I<atom:category> element.
1100
1101Acceptable I<atom:category> elements can be set like:
1102
1103    Controller::EntryCollection:
1104        collection:
1105            title: Diary
1106            categories:
1107              - fixed: yes
1108                scheme: http://example.com/cats/big3
1109                category:
1110                  - term: animal
1111                    label: animal
1112                  - term: vegetable
1113                    label: vegetable
1114                  - term: mineral
1115                    scheme: http://example.com/dogs/big3
1116                    label: mineral
1117
1118Acceptable media types is configured like:
1119
1120    Controller::MediaCollection:
1121        collection:
1122            title: Photo
1123            accept:
1124              - image/png
1125              - image/jpeg
1126              - image/gif
1127
1128
1129=head1 ERROR HANDLING
1130
1131See ERROR HANDLING in L<Catalyst::Controller::Atompub::Base>.
1132
1133
1134=head1 SAMPLES
1135
1136See SAMPLES in L<Catalyst::Controller::Atompub>.
1137
1138
1139=head1 SEE ALSO
1140
1141L<XML::Atom>
1142L<XML::Atom::Service>
1143L<Atompub>
1144L<Catalyst::Controller::Atompub>
1145
1146
1147=head1 AUTHOR
1148
1149Takeru INOUE  C<< <takeru.inoue _ gmail.com> >>
1150
1151
1152=head1 LICENCE AND COPYRIGHT
1153
1154Copyright (c) 2007, Takeru INOUE C<< <takeru.inoue _ gmail.com> >>. All rights reserved.
1155
1156This module is free software; you can redistribute it and/or
1157modify it under the same terms as Perl itself. See L<perlartistic>.
1158
1159
1160=head1 DISCLAIMER OF WARRANTY
1161
1162BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
1163FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
1164OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
1165PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
1166EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1167WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
1168ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
1169YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
1170NECESSARY SERVICING, REPAIR, OR CORRECTION.
1171
1172IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
1173WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
1174REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
1175LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
1176OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
1177THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
1178RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
1179FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
1180SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
1181SUCH DAMAGES.
Note: See TracBrowser for help on using the browser.