Mercurial > pub > Impl
view lib/IMPL/Web/Application/Resource.pm @ 409:f7eeafbd33da ref20150831
sync
author | cin |
---|---|
date | Sun, 13 Sep 2015 19:30:49 +0300 |
parents | c6e90e02dd17 |
children |
line wrap: on
line source
package IMPL::Web::Application::Resource; use strict; use constant { ResourceClass => __PACKAGE__ }; use Scalar::Util qw(blessed); use IMPL::lang qw(:hash :base); use IMPL::Const qw(:prop); use IMPL::declare { require => { Exception => 'IMPL::Exception', OpException => '-IMPL::InvalidOperationException', NotFoundException => 'IMPL::Web::NotFoundException', ResourceInterface => '-IMPL::Web::Application', HttpResponse => 'IMPL::Web::HttpResponse', HttpResponseResource => 'IMPL::Web::Application::HttpResponseResource', Loader => 'IMPL::Code::Loader' }, base => [ 'IMPL::Web::Application::ResourceBase' => '@_' ], props => [ access => PROP_RW, verbs => PROP_RW, children => PROP_RW ] }; __PACKAGE__->static_accessor(verbNames => [qw(get post put delete options head)]); __PACKAGE__->static_accessor(httpMethodPrefix => 'Http'); sub CTOR { my ($this, %args) = @_; my %verbs; my $httpPrefix = $this->httpMethodPrefix; foreach my $verb (@{$this->verbNames}) { my $method = exists $args{$verb} ? $args{$verb} : $this->can($httpPrefix . ucfirst($verb)); $verbs{$verb} = $method if $method; } hashApply(\%verbs,$args{verbs}) if ref($args{verbs}) eq 'HASH' ; $this->children($args{children} || $this->GetChildResources()); $this->access($args{access}) if $args{access}; $this->verbs(\%verbs); } sub _isInvokable { my ($this,$method) = @_; return (blessed($method) and $method->can('Invoke')) || ref($method) eq 'CODE' } sub _invoke { my ($this,$method,@args) = @_; if(blessed($method) and $method->can('Invoke')) { return $method->Invoke($this,@args); } elsif(ref($method) eq 'CODE' || (not(ref($method)) and $this->can($method))) { return $this->$method(@args); } else { die OpException->new("Can't invoke the specified method: $method"); } } sub HttpGet { shift->model; } sub AccessCheck { my ($this,$verb) = @_; $this->_invoke($this->access,$verb) if $this->access; } sub Fetch { my ($this,$childId) = @_; my $children = $this->children or die NotFoundException->new( $this->location->url, $childId ); if (ref($children) eq 'HASH') { if(my $child = $children->{$childId}) { return $this->_isInvokable($child) ? $this->_invoke($child, $childId) : $child; } else { die NotFoundException->new( $this->location->url, $childId ); } } elsif($this->_isInvokable($children)) { return $this->_invoke($children,$childId); } else { die OpException->new("Invalid resource description", $childId, $children); } } sub FetchChildResource { my ($this,$childId) = @_; my $info = $this->Fetch($childId); return $info if (is($info,ResourceInterface)); $info = { response => $info, class => HttpResponseResource } if is($info,HttpResponse); return $this->CreateChildResource($info, $childId) if ref($info) eq 'HASH'; die OpException->new("Invalid resource description", $childId, $info); } sub CreateChildResource { my ($this,$info, $childId) = @_; my $params = hashApply( { parent => $this, id => $childId, request => $this->request, class => ResourceClass }, $info ); $params->{model} = $this->_invoke($params->{model}) if $this->_isInvokable($params->{model}); my $factory = Loader->default->Require($params->{class}); return $factory->new(%$params); } sub GetChildResources { return {}; } 1; __END__ =pod =head1 NAME C<IMPL::Web::Application::Resource> - Ресурс C<REST> веб приложения =head1 SYNOPSIS =begin code use IMPL::require { Resource => 'IMPL::Web::Application::Resource', Security => 'IMPL::Security', NotFoundException => 'IMPL::Web::NotFoundException', ForbiddenException => 'IMPL::Web::ForbiddenException' }; my $model = Resource->new( get => sub { }, verbs => { # non-standart verbs placed here myverb => sub { } }, #child resources can be a hash children => { user => { # a resource class may be specified optionally # class => Resource, model => sub { return Security->principal }, # the default get implementation is implied # get => sub { shift->model }, access => sub { my ($this,$verb) = @_; die ForbiddenException->new() if Security->principal->isNobody } }, catalog => { get => sub { my $ctx = shift->application->ConnectDb()->AutoPtr(); return $ctx->products->find_rs({ in_stock => 1 }); }, # chid resource may be created dynamically children => sub { # binds model against the parent reource and id my ($this,$id) = @_; ($id) = ($id =~ /^(\w+)$/) or die NotFoundException->new($id); my $ctx = shift->application->ConnectDb()->AutoPtr(); my $item = $ctx->products->fetch($id); die NotFoundException->new() unless $item; # return parameters for the new resource return { model => $item, get => sub { shift->model } }; } }, # dynamically binds whole child resource. The result of binding is # the new resource or a hash with arguments to create one posts => sub { my ($this,$id) = @_; # this approach can be used to create a dynamic resource relaying # on the type of the model return Resource->new( id => $id, parent => $this, get => sub { shift->model } ); # ditto # parent and id will be mixed in automagically # return { get => sub { shift->model} } }, post_only => { get => undef, # remove GET verb implicitly post => sub { my ($this) = @_; } } } ); =end code Альтернативный вариант для создания класса ресурса. =begin code package MyResource; use IMPL::declare { require => { ForbiddenException => 'IMPL::Web::ForbiddenException' }, base => [ 'IMPL::Web::Application::Resource' => '@_' ] }; sub ds { my ($this) = @_; $this->context->{ds} ||= $this->application->ConnectDb(); } sub InvokeHttpVerb { my $this = shift; $this->ds->Begin(); my $result = $this->next::method(@_); # in case of error the data context will be disposed and the transaction # will be reverted $this->ds->Commit(); return $result; } # this method is inherited by default # sub HttpGet { # shift->model # # } sub HttpPost { my ($this) = @_; my %data = map { $_, $this->request->param($_) } qw(name description value); die ForbiddenException->new("The item with the scpecified name can't be created'") if(not $data{name} or $this->ds->items->find({ name => $data{name})) $this->ds->items->insert(\%data); return $this->NoContent(); } sub Fetch { my ($this,$childId) = @_; my $item = $this->ds->items->find({name => $childId}) or die NotFoundException->new(); # return parameters for the child resource return { model => $item, role => "item food" }; } =end code =head1 MEMBERS =head2 C<[get,set]verbs> Хеш с C<HTTP> методами. При попытке вызова C<HTTP> метода, которого нет в этом хеше приводит к исключению C<IMPL::Web::NotAllowedException>. =head2 C<[get,set]access> Метод для проверки прав доступа. Если не задан, то доспуп возможен для всех. =head2 C<[get,set]children> Дочерние ресурсы. Дочерние ресурсы могут быть описаны либо в виде хеша, либо в виде метода. =head3 C<HASH> Данный хещ содержит в себе таблицу идентификаторов дочерних ресурсов и их описаний. Описание каждого ресурса представляет собой либо функцию, либо параметры для создания ресурса C<CraeteChildResource>. Если описание в виде функции, то она должна возвращать либо объект типа ресурс либо параметры для его создания. =head3 C<CODE> Если дочерние ресурсы описаны в виде функции (возможно использовать имя метода класса текущего ресурса), то для получения дочернего ресурса будет вызвана функция с параметрами C<($this,$childId)>, где C<$this> - текущий ресурс, C<$childId> - идентификатор дочернего ресурса, который нужно вернуть. Данная функция должна возвратить либо объект типа ресурс, либо ссылку на хеш с параметрами для создания оного при помощи метода C<CreateChildResource($params,$childId)>. =head2 C<[virtual]Fetch($childId)> Метод для получения дочернего ресурса. Возвращает параметры для создания дочернего ресурса, либо уже созданный ресурс. Создание дочернего ресурса происходит при помощи метода C<CreateChildResource()> который добавляет недостающие параметры к возвращенным в данным методом и создает новый ресурс =head2 C<CreateChildResource($params,$id)> Создает новый дочерний ресурс с указанным идентификатором и параметрами. Автоматически заполняет параметры =over =item * C<parent> =item * C<id> =item * C<request> =back Тип создаваемого ресурса C<IMPL::Web::Application::Resource>, либо указывается в параметре C<class>. =head2 C<[virtual]HttpGet()> Реализует C<HTTP> метод C<GET>. По-умолчанию возвращает модель. Данный метод нужен для того, чтобы ресурс по-умолчанию поддерживал метод C<GET>, что является самым частым случаем, если нужно изменить данное поведение, нужно: =over =item * Передать в параметр конструктора C<get> значение undef =item * Переопределить метод C<HttpGet> =item * При проверке прав доступа выдать исключение =back =cut