Mercurial > pub > Impl
diff lib/IMPL/Web/Application/Resource.pm @ 407:c6e90e02dd17 ref20150831
renamed Lib->lib
author | cin |
---|---|
date | Fri, 04 Sep 2015 19:40:23 +0300 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/IMPL/Web/Application/Resource.pm Fri Sep 04 19:40:23 2015 +0300 @@ -0,0 +1,402 @@ +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 +