Mercurial > pub > Impl
view Lib/IMPL/Web/Application/RestResource.pm @ 214:4683002758aa
sync
author | sergey |
---|---|
date | Mon, 06 Aug 2012 17:27:47 +0400 |
parents | 292226770180 |
children | e9fd7ff3f54c |
line wrap: on
line source
package IMPL::Web::Application::RestResource; use strict; use IMPL::lang qw(:declare :constants is :hash); use IMPL::Exception(); use IMPL::declare { require => { ForbiddenException => 'IMPL::Web::ForbiddenException', NotFoundException => 'IMPL::Web::NotFoundException', InvalidOpException => '-IMPL::InvalidOperationException', ArgumentException => '-IMPL::InvalidArgumentException', TTransform => '-IMPL::Transform', TResolve => '-IMPL::Config::Resolve', CustomResource => 'IMPL::Web::Application::RestCustomResource' }, base => { 'IMPL::Web::Application::RestCustomResource' => '@_' } }; BEGIN { public property target => PROP_GET | PROP_OWNERSET; public property index => PROP_GET | PROP_OWNERSET; public property fetch => PROP_GET | PROP_OWNERSET; public property methods => PROP_GET | PROP_OWNERSET; public property childRegex => PROP_GET | PROP_OWNERSET; public property enableForms => PROP_GET | PROP_OWNERSET; } sub CTOR { my ($this,%args) = @_; die ArgumentException->new("target") unless $this->target; $this->final($this->childRegex ? 0 : 1); $this->methods({}) unless $this->methods; $this->index($this->get) unless defined $this->index; if ($this->enableForms) { $this->methods->{create} = { get => $this->get, post => $this->post, final => 1 # this resource doesn't have any children } if $this->post; $this->methods->{edit} = { get => $this->get, post => $this->put, final => 1 # this resource doesn't have any children } if $this->put; $this->methods->{delete} { get => $this->get, post => $this->delete, final => 1 # this resource doesn't have any children } if $this->delete; } } # создает дочерний ресурс из описания, однако все методы созданного # ресурса переадресуются к его родителю, это нужно, чтобы публиковать # методы и свойства объекта sub _CreateSubResource { my ($this,$resource,$id) = @_; my %methods = map { my $method = $resource->{$_}; $_ => sub { my ($this,$action) = @_; return $this->parent->InvokeMember($method,$action); }; } grep $resource->{$_}, qw(get post put delete); return CustomResource->new( %methods, final => $resource->{final}, parent => $this, id => $id, contract => $this->contract ); } sub FetchChildResource { my ($this,$id,$action) = @_; my $rx = $this->childRegex; my $res; if (length $id == 0) { my $method = $this->index; die ForbiddenException->new() unless $method; $res = $this->InvokeMember($method,$action); } elsif ($this->methods and my $resource = $this->methods->{$id}) { return $this->_CreateSubResource($resource,$id); } elsif ($rx and $id =~ m/^$rx$/ and my $method = $this->fetch) { $method = { method => $method, parameters => 'id' } unless ref $method; $res = $this->InvokeMember($method,$action, { id => $id } ); } die NotFoundException->new() unless defined $res; return $this->contract->Transform($res, {parent => $this, id => $id} ); } sub InvokeMember { my ($this,$method,$action,$predefined) = @_; die ArgumentException->new("method","No method information provided") unless $method; #normalize method info if (not ref $method) { $method = { method => $method }; } if (ref $method eq 'HASH') { my $member = $method->{method} or die InvalidOpException->new("A member name isn't specified"); $member = $member->Invoke($this) if eval { $member->isa(TResolve) }; my @args; if (my $params = $method->{parameters}) { if (ref $params eq 'HASH') { @args = map { $_, $this->MakeParameter($params->{$_},$action,$predefined) } keys %$params; } elsif (ref $params eq 'ARRAY') { @args = map $this->MakeParameter($_,$action,$predefined), @$params; } else { @args = ($this->MakeParameter($params,$action,$predefined)); } } return $this->target->$member(@args); } elsif (ref $method eq TResolve) { return $method->Invoke($this); } elsif (ref $method eq 'CODE') { return $method->($this,$action); } else { die InvalidOpException->new("Unsupported type of the method information", ref $method); } } sub MakeParameter { my ($this,$param,$action,$predefined) = @_; my $params = hashApply( { id => $this->id, action => $action, query => $action->query }, $predefined || {} ); if ($param) { if (is $param, TTransform ) { return $param->Transform($action->query); } elsif ($param and not ref $param) { return $params->{$param} || $action->query->param($param); } else { die InvalidOpException->new("Unsupported parameter mapping", $param); } } else { return undef; } } 1; __END__ =pod =head1 NAME C<IMPL::Web::Application::RestResource> - ресурс Rest вебсервиса. =head1 SYNOPSIS =begin text [REQUEST] GET /artists [RESPONSE] <artists> <artist id="1"> <name>The Beatles <name/> </atrist> <artist id="2"> <name>Bonobo</name> </artist> </artists> [REQUEST] GET /artists/1/cds?title='Live at BBC' [RESPONSE] <cds> <cd id="14"> <title>Live at BBC 1</title> </cd> <cd id="15"> <title>Live at BBC 2</title> </cd> </cds> [REQUEST] GET /cds/15 [RESPONSE] <cd id="15"> <title>Live at BBC 2</title> </cd> =end text =begin code use IMPL::require { TRes => 'IMPL::Web:Application::RestResource', DataContext => 'My::App::DataContext' }; my $cds = TRes->new( DataContext->Default, { methods => { history => { get => { method => 'GetHistory', parameters => [qw(from to)] }, }, rating => { get => { method => 'GetRating' } post => { method => 'Vote', parameters => [qw(id rating comment)] } } } index => { method => 'search', paremeters => [qw(filter page limit)] }, fetch => 'GetItemById' } ); =end code =head1 DESCRIPTION Каждый ресурс представляет собой коллекцию и реализует методы C<HTTP> C<GET,POST,PUT,DELETE>. Вызов каждого из этих методов позволяет выполнить одну из операций над ресурсом, однако операций может быть больше, для этого создаются дочерние ресурсы (каждый из которых также может иметь четыре метода C<GET,POST,PUT,DELETE>), однако обращения к методам у дочерних ресурсов отображаются в вызовы методов у родительского ресурса. Такой подход позволяет расширить функциональность не изменяя стандарт C<HTTP>, а также обойти ограничения браузеров на методы C<PUT,DELETE>. Данный тип ресутсов расчитан на использование с конфигурацией, которую можно будет сохранить или прочитать, например, из файла. Для этого у ресурса есть ряд настроек, которые позволяют в простой форме задать отображения между C<HTTP> методами и методами объекта представленного данным ресурсом. =head2 HTTP METHODS =head3 C<GET> Возвращает данные из текущего ресурса. Обращение к данному методу не должно вносить изменений в ресурсы. =head3 C<PUT> Обновляет ресурс. Повторное обращение к данному методу должно приводить к одному и томуже результату. =head3 C<DELETE> Удаляет ресурс. =head3 C<POST> Данный метод может вести себя как угодно, однако обычно он используется для добавления нового дочернего ресурса в коллекцию,также может использоваться для вызова метода, в случае если происходит публикация методов в качестве дочерних ресурсов. =head1 BROWSER COMPATIBILITY Однако существует проблема с браузерами, поскольку тег C<< <form> >> реализет только методы C<GET,POST>. Для решения данной проблемы используется режим совместимости C<enableForms>. В случае когда данный режим активен, автоматически публикуются дочерние ресурсы C<create,edit,delete>. Данные ресуры пбликуются как методы, что означает то, что обращения к ним будут превращены в выполнения соответсвующих методов на родительском объекте. =head2 C<create> По сути данные ресурс не является необходимостью, однако создается для целостности модели. =head3 C<GET> Передает управление методу C<get> =head3 C<POST> Передает управление методу C<post> =head2 C<edit> =head3 C<GET> Передает управление методу C<get> =head3 C<POST> Передает управление методу C<put>, как если бы он был выполнен непосредственно у родительского ресурса. =head2 C<delete> =head3 C<GET> Передает управление методу C<get> =head3 C<POST> Передает управление методу C<delete>, , как если бы он был выполнен непосредственно у родительского ресурса. =head1 METHOD DEFINITIONS Все методы ресурсов данного типа задаются описаниями, хранящимися в соответствующих свойствах. Когда наступает необходимость вызова соответствующего метода, его описание бедется из свойства и передается методу C<InvokeMember>, который и производит вызов. =head2 C<HASH> Содержит в себе описание метода, который нужно вызвать, а также его параметры. =over =item C<method> Имя метода который будет вызван. =item C<paremeters> Описание параметров метода, может быть либо массивом, либо хешем, либо простым значением. =over =item C<ARRAY> Метод получает список параметров, каждый элемент данного массива будет превращен в параметр при помощи метода C<MakeParameter> =item C<HASH> Метод получает список параметров, который состоит пар ключ-значение, каждое значение данного хеша будет превращено в зачение параметра метода при помощи метода C<MakeParameter>. Ключи хеша изменениям не подвергаются. =item Простое значение Метод получает одно значение, которое будет получено из текущего при помощи C<MakeParameter>. =back =back =head2 C<CODE> Если в описании метода находится ссылка на функцию, то эта функция будет вызвана с параметрами. Данный вариант полезен когда ресурсы создаются програмно обычного механизма описаний не достаточно для реализации требуемого функционала. =over =item C<$resource> Текущий ресурс у которого производится вызов метода. =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. =back =head2 Простое значение Интерпретируется как имя метода у объекта данных текущего ресурса. =head1 MEMBERS =head2 C<[get]id> Идентификатор текущего ресурса. =head2 C<[get]target> Объект данных (может быть и класс, поскольку у него будут только вызываться методы), обеспечивающий функционал ресурса. =head2 C<[get]parent> Родительский ресурс, в котором находится текущий ресурс. Может быть C<undef>, если текущий ресурс является корнем. =head2 C<[get]methods> Содержит описания методов, которые будут публиковаться как дочерние ресурсы. =head2 C<[get]childRegex> Содержит регулярное выражение для идентификаторов дочерних объектов. Если оно не задано, то данный ресурс не является коллекцией. =head2 C<[get]fetch> Содержит описание метода для получения дочернего объекта. Если данный метод отсутствует, то дочерние ресурсы не получится адресовать относительно данного. По умолчанию получает идентификатор дочернего ресурса первым параметром. =head2 C<[get]index> Описание метода для получения списка дочерних объектов. По умолчанию не получает параметров. =head2 C<[get]post> Описание метода для добавление дочернего ресурса. По умолчанию получает объект C<CGI> описывабщий текущий запрос первым параметром. =head2 C<[get]put> Описание метода для обновления дочернего ресурса. По умолчанию получает объект C<CGI> текущего запроса. =head2 C<[get]delete> Описание метода для удаления дочернего ресурса. По умолчанию не получает параметров. =head2 C<GetImpl($action)> =over =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. =back Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. =head2 C<PutImpl($action)> =over =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. =back Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. =head2 C<PostImpl($action)> =over =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. =back Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. =head2 C<DeleteImpl($action)> =over =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. =back Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. =head2 C<InvokeMember($memberInfo,$action,$params)> =over =item C<$memberInfo> Описание члена внутреннего объекта C<target>, который нужно вызвать. =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. =item C<$params> Ссылка на хеш с предопределенными параметрами. =back Вызывает метод внутреннего объекта C<target>, предварительно подготовив параметры на основе описания C<$memberInfo> и при помощи С<MakeParameter()>. =head2 C<MakeParameter($paramDef,$action)> =over =item C<$paramDef> Описание параметра, может быть C<IMPL::Transform> или простая строка. Если описание параметра - простая строка, то ее имя либо =over =item C<id> Идентификатор ресурса =item C<query> Объект C<CGI> текущего запроса =item C<action> Текущий запрос C<IMPL::Web::Application::Action> =item C<любое другое значение> Интерпретируется как параметр текущего запроса. =back Если описание параметра - объект C<IMPL::Transform>, то будет выполнено это преобразование над C<CGI> объектом текущего запроса C<< $paramDef->Transform($action->query) >>. =item C<$action> Текущий запрос =back =cut