root/lang/perl/Catalyst-Controller-Atompub/trunk/lib/Catalyst/Controller/Atompub/Collection.pm @ 6742

Revision 6742, 29.9 kB (checked in by takemaru, 5 years ago)

lang/perl/Catalyst-Controller-Atompub: 0.3.3 released. fix many bugs, 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;
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;
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;
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 );
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
485package Catalyst::Controller::Atompub::Collection::Resource;
486
487use Atompub::MediaType qw( media_type );
488
489use base qw( Class::Accessor::Fast );
490
491__PACKAGE__->mk_accessors( qw( uri type body ) );
492
493sub edited {
494    my $self = shift;
495    if (@_) {
496        $self->{edited} = $_[0];
497    }
498    else {
499        warn '$collection->$resource->edited is DEPRECATED, and see $collection->edited';
500        return $self->{edited};
501    }
502}
503
504sub is_collection {
505    my $self = shift;
506    my $media_type = media_type( $self->type ) || return;
507    return $media_type->is_a('feed');
508}
509
510sub is_entry {
511    my $self = shift;
512    my $media_type = media_type( $self->type ) || return;
513    return $media_type->is_a('entry');
514}
515
516sub is_media {
517    my $self = shift;
518    my $media_type = media_type( $self->type ) || return;
519    return ! $media_type->is_a('application/atom+xml');
520}
521
522sub serialize {
523    my $self = shift;
524    my $body = $self->body;
525    UNIVERSAL::can( $body, 'as_xml' ) ? $body->as_xml : $body;
526}
527
5281;
529__END__
530
531=head1 NAME
532
533Catalyst::Controller::Atompub::Collection
534- A Catalyst controller for the Atom Collection Resources
535
536
537=head1 SYNOPSIS
538
539    # Use the Catalyst helper
540    $ perl script/myatom_create.pl controller MyCollection Atompub::Collection
541
542    # And edit lib/MyAtom/Controller/MyCollection.pm
543    package MyAtom::Controller::MyCollection;
544    use base 'Catalyst::Controller::Atompub::Collection';
545
546    # List resources in a Feed Document, which must be implemented in
547    # the mehtod with "Atompub(list)" attribute
548    sub get_feed :Atompub(list) {
549        my ( $self, $c ) = @_;
550   
551        # Skeleton of the Feed (XML::Atom::Feed) was prepared by
552        # C::C::Atompub
553        my $feed = $self->collection_resource->body;
554   
555        # Retrieve Entries sorted in descending order
556        my $rs = $c->model('DBIC::Entries')
557                   ->search( {}, { order_by => 'edited desc' } );
558   
559        # Add Entries to the Feed
560        while ( my $entry_resource = $rs->next ) {
561            my $entry = XML::Atom::Entry->new( \$entry_resource->xml );
562            $feed->add_entry( $entry );
563        }
564   
565        # Return true on success
566        return 1;
567    }
568   
569    # Create new Entry in the method with "Atompub(create)" attribute
570    sub create_entry :Atompub(create) {
571        my ( $self, $c ) = @_;
572   
573        # URI of the new Entry, which was determined by C::C::Atompub
574        my $uri = $self->entry_resource->uri;
575   
576        # app:edited element, which was assigned by C::C::Atompub,
577        # is coverted into ISO 8601 format like '2007-01-01 00:00:00'
578        my $edited = $self->edited->iso;
579
580        # POSTed Entry (XML::Atom::Entry)
581        my $entry = $self->entry_resource->body;
582   
583        # Create new Entry
584        $c->model('DBIC::Entries')->create( {
585            uri    => $uri,
586            edited => $edited,
587            xml    => $entry->as_xml,
588        } );
589   
590        # Return true on success
591        return 1;
592    }
593   
594    # Search the requested Entry in the method with "Atompub(read)"
595    # attribute
596    sub get_entry :Atompub(read) {
597        my ( $self, $c ) = @_;
598   
599        my $uri = $c->req->uri;
600   
601        # Retrieve the Entry
602        my $rs = $c->model('DBIC::Entries')->find( { uri => $uri } );
603   
604        # Set the Entry
605        my $entry = XML::Atom::Entry->new( \$rs->xml );
606        $self->entry_resource->body( $entry );
607   
608        # Return true on success
609        return 1;
610    }
611   
612    # Update the requested Entry in the method with "Atompub(update)"
613    # attribute
614    sub update_entry :Atompub(update) {
615        my ( $self, $c ) = @_;
616   
617        my $uri = $c->req->uri;
618   
619        # app:edited element, which was assigned by C::C::Atompub,
620        # is coverted into ISO 8601 format like '2007-01-01 00:00:00'
621        my $edited = $self->edited->iso;
622
623        # PUTted Entry (XML::Atom::Entry)
624        my $entry = $self->entry_resource->body;
625   
626        # Update the Entry
627        $c->model('DBIC::Entries')->find( { uri => $uri } )
628                                  ->update( {
629                                      uri => $uri,
630                                      edited => $edited,
631                                      xml => $entry->as_xml,
632                                  } );
633   
634        # Return true on success
635        return 1;
636    }
637   
638    # Delete the requested Entry in the method with "Atompub(delete)"
639    # attribute
640    sub delete_entry :Atompub(delete) {
641        my ( $self, $c ) = @_;
642   
643        my $uri = $c->req->uri;
644   
645        # Delete the Entry
646        $c->model('DBIC::Entries')->find( { uri => $uri } )->delete;
647   
648        # Return true on success
649        return 1;
650    }
651       
652    # Access to http://localhost:3000/mycollection and get Feed Document
653
654
655=head1 DESCRIPTION
656
657Catalyst::Controller::Atompub::Collection provides the following features:
658
659=over 4
660
661=item * Pre-processing requests
662
663L<Catalyst::Controller::Atompub::Collection> pre-processes the HTTP requests.
664All you have to do is just writing CRUD operations in the subroutines
665with I<Atompub> attribute.
666
667=item * Media Resource support
668
669Media Resources (binary data) as well as Entry Resources are supported.
670A Media Link Entry, which has an I<atom:link> element to the newly created Media Resource,
671is given by L<Catalyst::Controller::Atompub::Collection>.
672
673=item * Media type check
674
675L<Catalyst::Controller::Atompub::Collection> checks a media type of
676the POSTed/PUTted resource based on collection configuration.
677
678=item * Category check
679
680L<Catalyst::Controller::Atompub::Collection> checks
681I<atom:category> elements in the POSTed/PUTted Entry Document
682based on collection configuration.
683
684=item * Cache controll and versioning
685
686Cache controll and versioning are enabled just by overriding C<find_version> method,
687which returns I<ETag> and/or I<Last-Modified> header.
688
689=item * Naming resources by I<Slug> header
690
691Resource URIs are determined based on I<Slug> header if exists.
692If the I<Slug> header is "Entry 1", the resource URI will be like:
693
694    http://localhost:3000/mycollection/entry_1.atom
695
696The default naming rules can be changed by overriding C<make_edit_uri> method.
697
698=back
699
700
701=head1 SUBCLASSING
702
703One or more subclasses are required in your Atompub server implementation.
704In the subclasses, methods with the following attributes must be defined.
705
706
707=head2 sub xxx :Atompub(list)
708
709Lists resources in a Feed Document.
710
711This method is expected to add Entries and other elements to a skeleton of the Feed.
712The following accessors can be used.
713
714=over 2
715
716=item - $controller->collection_resource->uri
717
718URI of Collection
719
720=item - $controller->collection_resource->body
721
722Skeleton of Feed (L<XML::Atom::Feed>)
723
724=back
725
726Returns true on success, false otherwise.
727
728
729=head2 sub xxx :Atompub(create)
730
731Creates new resource.
732
733=over 4
734
735=item * In Collections with Entry Resources
736
737The implementation is expected to insert the new Entry to your model, such as L<DBIx::Class>.
738The following accessors can be used.
739
740=over 2
741
742=item - $controller->entry_resource->uri
743
744URI of New Entry
745
746=item - $controller->entry_resource->edited
747
748I<app:edited> element of New Entry
749
750=item - $controller->entry_resource->body
751
752New Entry (L<XML::Atom::Entry>)
753
754=back
755
756
757=item * In Collections with Media Resources
758
759The implementation is expected to insert new Media Link Entry as well as new Media Resource
760to your model, such as L<DBIx::Class>.
761
762The following accessors can be used for the Media Resource.
763
764=over 2
765
766=item - $controller->media_resource->uri
767
768URI of New Media Resource
769
770=item - $controller->media_resource->edited
771
772I<app:edited> element of New Media Resource
773
774=item - $controller->media_resource->type
775
776Media type of New Media Resource
777
778=item - $controller->media_resource->body
779
780New Media Resource (a byte string)
781
782=back
783
784
785The following accessors can be used for Media Link Entry.
786
787=over 2
788
789=item - $controller->media_link_entry->uri
790
791URI of New Media Link Entry
792
793=item - $controller->media_link_entry->edited
794
795I<app:edited> element of New Media Link Entry
796
797=item - $controller->media_link_entry->body
798
799New Media Link Entry (L<XML::Atom::Entry>)
800
801=back
802
803
804=back
805
806Returns true on success, false otherwise.
807
808
809=head2 sub xxx :Atompub(read)
810
811Searchs the requested resource.
812
813=over 4
814
815=item * In Collections with Entry Resources
816
817The implementation is expected to search the Entry,
818which must be stored in C<body> accessor described below.
819The following accessors can be used.
820
821=over 2
822
823=item - $controller->entry_resource->body
824
825Entry (L<XML::Atom::Entry>)
826
827=back
828
829=item * In Collections with Media Resources
830
831The implementation is expected to search Media Resource or Media Link Entry,
832which must be stored in C<body> accessor described below.
833The following accessors can be used for the Media Resource.
834
835=over 2
836
837=item - $controller->media_resource->type
838
839Media type of Media Resource
840
841=item - $controller->media_resource->body
842
843Media Resource (a byte string)
844
845=back
846
847The following accessors can be used for the Media Link Entry.
848
849=over 2
850
851=item - $controller->media_link_entry->body
852
853Media Link Entry (L<XML::Atom::Entry>)
854
855=back
856
857=back
858
859Returns true on success, false otherwise.
860
861
862=head2 sub xxx :Atompub(update)
863
864Updates the requested resource.
865
866
867=over 4
868
869=item * In Collections with Entry Resources
870
871The implementation is expected to update the Entry.
872The following accessors can be used.
873
874=over 2
875
876=item - $controller->entry_resource->uri
877
878URI of Entry
879
880=item - $controller->entry_resource->edited
881
882I<app:edited> element of Entry
883
884=item - $controller->entry_resource->body
885
886Entry (L<XML::Atom::Entry>)
887
888=back
889
890
891=item * In Collections with Media Resources
892
893The implementation is expected to update the Media Resource or the Media Link Entry.
894The following accessors can be used for the Media Resource.
895
896=over 2
897
898=item - $controller->media_resource->uri
899
900URI of Media Resource
901
902=item - $controller->media_resource->edited
903
904I<app:edited> element of Media Resource
905
906=item - $controller->media_resource->type
907
908Media type of Media Resource
909
910=item - $controller->media_resource->body
911
912Media Resource (a byte string)
913
914=back
915
916
917The following accessors can be used for the Media Link Entry.
918
919=over 2
920
921=item - $controller->media_link_entry->uri
922
923URI of Media Link Entry
924
925=item - $controller->media_link_entry->edited
926
927I<app:edited> element of Media Link Entry
928
929=item - $controller->media_link_entry->body
930
931Media Link Entry (L<XML::Atom::Entry>)
932
933=back
934
935=back
936
937Returns true on success, false otherwise.
938
939
940=head2 sub xxx :Atompub(delete)
941
942Deletes the requested resource.
943
944The implementation is expected to delete the resource.
945If the collection contains Media Resources,
946corresponding Media Link Entry must be deleted at once.
947
948Returns true on success, false otherwise.
949
950
951=head1 METHODS
952
953The following methods can be overridden to change the default behaviors.
954
955
956=head2 $controller->find_version( $uri )
957
958By overriding C<find_version> method, cache control and versioning are enabled.
959
960The implementation is expected to return I<ETag> and/or I<Last-Modified> value
961of the requested URI:
962
963    package MyAtom::Controller::MyCollection;
964
965    sub find_version {
966        my ( $self, $c, $uri ) = @_;
967
968        # Retrieve ETag and/or Last-Modified of $uri
969
970        return ( etag => $etag, last_modified => $last_modified );
971    }
972
973When a resource of the URI does not exist, the implementation must return an empty array.
974
975The behavior of Atompub server will be changed in the following manner:
976
977=over 4
978
979=item * On GET request
980
981Status code of 304 (Not Modified) will be returned,
982if the requested resource has not been changed.
983
984=item * On PUT request
985
986Status code of 412 (Precondition Failed) will be returned,
987if the current version of the resource that a client is modifying is not
988the same as the version that the client is basing its modifications on.
989
990=back
991
992
993=head2 $controller->make_edit_uri( $c, [ @args ]);
994
995By default, if the I<Slug> header is "Entry 1", the resource URI will be like:
996
997    http://localhost:3000/mycollection/entry_1.atom
998
999This default behavior can be changed by overriding C<find_version> method:
1000
1001    package MyAtom::Controller::MyCollection;
1002
1003    sub make_edit_uri {
1004        my ( $self, $c, @args ) = @_;
1005
1006        my @uris = $self->SUPER::make_edit_uri( $c, @args );
1007
1008        # Modify @uris as you like
1009
1010        return @uris;
1011    }
1012
1013Arguments @args are media types of POSTed resources.
1014
1015This method returns an array of resource URIs;
1016the first element is a URI of the Entry Resource (including Media Link Entry),
1017and the second one is a URI of the Media Resource if exists.
1018
1019
1020=head2 $controller->do_list
1021
1022=head2 $controller->do_create
1023
1024=head2 $controller->do_read
1025
1026=head2 $controller->do_update
1027
1028=head2 $controller->do_delete
1029
1030
1031=head1 ACCESSORS
1032
1033=head2 $controller->resource
1034
1035=head2 $controller->rc
1036
1037An accessor for a resource object except Media Link Entry.
1038
1039
1040=head2 $controller->collection_resource
1041
1042An accessor for a Collection Resource object.
1043
1044
1045=head2 $controller->entry_resource
1046
1047An accessor for an Entry Resource objecgt.
1048
1049
1050=head2 $controller->media_resource
1051
1052An accessor for a Media Resource object.
1053
1054
1055=head2 $controller->media_link_entry
1056
1057An accessor for a Media Link Entry object.
1058
1059
1060=head2 $controller->edited
1061
1062An accessor for a app:edited, which is applied for the POSTed/PUTted Entry Resource.
1063
1064
1065=head1 INTERNAL INTERFACES
1066
1067=head2 $controller->auto
1068
1069=head2 $controller->default
1070
1071=head2 $controller->edit_uri
1072
1073=head2 $controller->_list
1074
1075=head2 $controller->_create
1076
1077=head2 $controller->_read
1078
1079=head2 $controller->_update
1080
1081=head2 $controller->_delete
1082
1083=head2 $controller->_is_modified
1084
1085=head2 $controller->create_action
1086
1087
1088=head1 FEED PAGING
1089
1090This module does not provide paging of Feed Documents.
1091Paging mechanism should be implemented in a method with "Atompub(list)" attribute.
1092
1093
1094=head1 CONFIGURATION
1095
1096By default (no configuration), Collections accept Entry Documents
1097(application/atom+xml) and any I<atom:category> element.
1098
1099Acceptable I<atom:category> elements can be set like:
1100
1101    Controller::EntryCollection:
1102        collection:
1103            title: Diary
1104            categories:
1105              - fixed: yes
1106                scheme: http://example.com/cats/big3
1107                category:
1108                  - term: animal
1109                    label: animal
1110                  - term: vegetable
1111                    label: vegetable
1112                  - term: mineral
1113                    scheme: http://example.com/dogs/big3
1114                    label: mineral
1115
1116Acceptable media types is configured like:
1117
1118    Controller::MediaCollection:
1119        collection:
1120            title: Photo
1121            accept:
1122              - image/png
1123              - image/jpeg
1124              - image/gif
1125
1126
1127=head1 ERROR HANDLING
1128
1129See ERROR HANDLING in L<Catalyst::Controller::Atompub::Base>.
1130
1131
1132=head1 SAMPLES
1133
1134See SAMPLES in L<Catalyst::Controller::Atompub>.
1135
1136
1137=head1 SEE ALSO
1138
1139L<XML::Atom>
1140L<XML::Atom::Service>
1141L<Atompub>
1142L<Catalyst::Controller::Atompub>
1143
1144
1145=head1 AUTHOR
1146
1147Takeru INOUE  C<< <takeru.inoue _ gmail.com> >>
1148
1149
1150=head1 LICENCE AND COPYRIGHT
1151
1152Copyright (c) 2007, Takeru INOUE C<< <takeru.inoue _ gmail.com> >>. All rights reserved.
1153
1154This module is free software; you can redistribute it and/or
1155modify it under the same terms as Perl itself. See L<perlartistic>.
1156
1157
1158=head1 DISCLAIMER OF WARRANTY
1159
1160BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
1161FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
1162OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
1163PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
1164EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1165WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
1166ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
1167YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
1168NECESSARY SERVICING, REPAIR, OR CORRECTION.
1169
1170IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
1171WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
1172REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
1173LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
1174OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
1175THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
1176RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
1177FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
1178SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
1179SUCH DAMAGES.
Note: See TracBrowser for help on using the browser.