Mercurial > pub > Impl
changeset 202:5146e17a7b76
IMPL::Web::Application::RestResource fixes, documentation
author | sergey |
---|---|
date | Wed, 25 Apr 2012 02:49:23 +0400 |
parents | 0c018a247c8a |
children | 68a59c3358ff |
files | Lib/IMPL/Serialization.pm Lib/IMPL/Web/Application/RestBaseResource.pm Lib/IMPL/Web/Application/RestCustomResource.pm Lib/IMPL/Web/Application/RestResource.pm Lib/IMPL/Web/Handler/RestController.pm |
diffstat | 5 files changed, 266 insertions(+), 183 deletions(-) [+] |
line wrap: on
line diff
--- a/Lib/IMPL/Serialization.pm Tue Apr 24 19:52:07 2012 +0400 +++ b/Lib/IMPL/Serialization.pm Wed Apr 25 02:49:23 2012 +0400 @@ -242,7 +242,7 @@ return 1; } - my $refObj = $this->{$ObjectFactory} ?$this->{$ObjectFactory}->($rhObject->{'Type'},$rhObject->{'Data'},$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef) : DefaultFactory($rhObject->{'Type'},$rhObject->{'Data'} || [],$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef); + my $refObj = $this->{$ObjectFactory} ?$this->{$ObjectFactory}->($rhObject->{'Type'},$rhObject->{'Data'},$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef) : DefaultFactory($rhObject->{'Type'},$rhObject->{'Data'},$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef); die new Exception("Trying to close a non existing oject") if not $rhObject; @@ -329,6 +329,7 @@ return \$Data; } } elsif ($Type eq 'ARRAY') { + $Data ||= []; die new Exception("Invalid a deserialization context when deserializing ARRAY") if not ref $Data and defined $Data; if (not ref $refSurogate) { my @Array; @@ -339,6 +340,7 @@ } return $refSurogate; } elsif ($Type eq 'HASH') { + $Data ||= []; die new Exception("Invalid a deserialization context when deserializing HASH") if not ref $Data and defined $Data; if (not ref $refSurogate) { $refSurogate = {}; @@ -348,6 +350,7 @@ } return $refSurogate; } elsif ($Type eq 'REF') { + $Data ||= []; die new Exception("Invalid a deserialization context when deserializing REF") if not ref $Data and defined $Data; if (not ref $refSurogate) { my $ref = $Data->[1];
--- a/Lib/IMPL/Web/Application/RestBaseResource.pm Tue Apr 24 19:52:07 2012 +0400 +++ b/Lib/IMPL/Web/Application/RestBaseResource.pm Wed Apr 25 02:49:23 2012 +0400 @@ -81,61 +81,6 @@ return undef; } -sub InvokeMember { - my ($this,$method,$action) = @_; - - 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"); - - my @args; - - if (my $params = $method->{parameters}) { - if (ref $params eq 'HASH') { - @args = map { - $_, - $this->MakeParameter($params->{$_},$action) - } keys %$params; - } elsif (ref $params eq 'ARRAY') { - @args = map $this->MakeParameter($_,$action), @$params; - } else { - @args = ($this->MakeParameter($params,$action)); - } - } - return $this->target->$member(@args); - } elsif (ref $method eq TResolve) { - return $method->Invoke($this->target); - } 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) = @_; - - if ($param) { - if (is $param, TTransform ) { - return $param->Transform($this,$action->query); - } elsif ($param and not ref $param) { - return $action->query->param($param); - } else { - die new InvalidOpException->new("Unsupported parameter mapping", $param); - } - } else { - return undef; - } -} - 1;
--- a/Lib/IMPL/Web/Application/RestCustomResource.pm Tue Apr 24 19:52:07 2012 +0400 +++ b/Lib/IMPL/Web/Application/RestCustomResource.pm Wed Apr 25 02:49:23 2012 +0400 @@ -21,12 +21,6 @@ public property delete => PROP_GET | PROP_OWNERSET; } -sub CTOR { - my ($this) = @_; - - die ArgumentException->new("parent") unless $this->parent; -} - sub FetchChildResource { my ($this,$id,$action) = @_; @@ -60,4 +54,10 @@ return $this->InvokeMember($method,$action); } +sub InvokeMember { + my ($this,$method,$action) = @_; + + return $this->$method($action); +} + 1; \ No newline at end of file
--- a/Lib/IMPL/Web/Application/RestResource.pm Tue Apr 24 19:52:07 2012 +0400 +++ b/Lib/IMPL/Web/Application/RestResource.pm Wed Apr 25 02:49:23 2012 +0400 @@ -1,7 +1,7 @@ package IMPL::Web::Application::RestResource; use strict; -use IMPL::lang qw(:declare :constants is); +use IMPL::lang qw(:declare :constants is :hash); use IMPL::Exception(); use IMPL::declare { @@ -12,7 +12,7 @@ ArgumentException => '-IMPL::InvalidArgumentException', TTransform => '-IMPL::Transform', TResolve => '-IMPL::Config::Resolve', - CustomResource => 'IMPL::Web::Application::CustomResource' + CustomResource => 'IMPL::Web::Application::RestCustomResource' }, base => { 'IMPL::Web::Application::RestCustomResource' => '@_' @@ -37,46 +37,50 @@ die ArgumentException->new("target") unless $this->target; $this->final($this->childRegex ? 0 : 1); + $this->methods({}) unless $this->methods; if ($this->enableForms) { $this->methods->{create} = { - get => \&_ParentGet, - post => \&_ParentPost, + get => $this->get, + post => $this->post, final => 1 # this resource doesn't have any children - }; + } if $this->post; $this->methods->{edit} = { - get => \&_ParentGet, - post => \&_ParentPut, + get => $this->get, + post => $this->put, final => 1 # this resource doesn't have any children - }; + } if $this->put; $this->methods->{delete} { - get => \&_ParentGet, - post => \&_ParentDelete + get => $this->get, + post => $this->delete, final => 1 # this resource doesn't have any children - } + } if $this->delete; } } -sub _ParentGet { - my ($this,$action) = @_; - return $this->parent->GetImpl($action); -} - -sub _ParentPut { - my ($this,$action) = @_; - return $this->parent->PutImpl($action); -} - -sub _ParentPost { - my ($this,$action) = @_; - return $this->parent->PostImpl($action); -} - -sub _ParentDelete { - my ($this,$action) = @_; - return $this->parent->DeleteImpl($action); +# создает дочерний ресурс из описания, однако все методы созданного +# ресурса переадресуются к его родителю, это нужно, чтобы публиковать +# методы и свойства объекта +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 { @@ -93,21 +97,19 @@ $res = $this->InvokeMember($method,$action); - } elsif (my $resource = $this->methods->{$id}) { - return CustomResource->new( - get => $resource->{get}, - post => $resource->{post}, - put => $resource->{put}, - delete => $resource->{delete}, - parent => $this, - id => $id, - target => $this->target - ); - - } elsif ($rx and $id =~ m/^$rx$/ and $method = $this->fetch) { + } 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 } ); - } else { - die ForbiddenException->new(); + } die NotFoundException->new() unless defined $res; @@ -115,6 +117,74 @@ 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__ @@ -192,7 +262,10 @@ } } } - list => 'search', + index => { + method => 'search', + paremeters => [qw(filter page limit)] + }, fetch => 'GetItemById' } ); @@ -203,47 +276,41 @@ Каждый ресурс представляет собой коллекцию и реализует методы C<HTTP> C<GET,POST,PUT,DELETE>. -Ресурсы выстраиваются в иерархию, на основе пути. Поиск конечного реурса происходит последовательным -вызовом метода GET с именем очередного ресурса. - +Вызов каждого из этих методов позволяет выполнить одну из операций над ресурсом, однако +операций может быть больше, для этого создаются дочерние ресурсы (каждый из которых также +может иметь четыре метода C<GET,POST,PUT,DELETE>), однако обращения к методам у дочерних +ресурсов отображаются в вызовы методов у родительского ресурса. + +Такой подход позволяет расширить функциональность не изменяя стандарт C<HTTP>, а также обойти +ограничения браузеров на методы C<PUT,DELETE>. + +Данный тип ресутсов расчитан на использование с конфигурацией, которую можно будет +сохранить или прочитать, например, из файла. Для этого у ресурса есть ряд настроек, +которые позволяют в простой форме задать отображения между C<HTTP> методами и методами +объекта представленного данным ресурсом. + =head2 HTTP METHODS =head3 C<GET> -Возвращает коллекцию дочерних ресурсов. - -=head3 C<GET {id}> +Возвращает данные из текущего ресурса. Обращение к данному методу не должно вносить +изменений в ресурсы. -Возвращает дочерний объект с идентификатором C<id> - -=head3 C<GET {method}> +=head3 C<PUT> -Вызывает метод C<method> и возвращает его результаты. При публикации методов доступных -через C<GET> данные методы не должны вносить изменений в предметную область. - -=head3 C<PUT {id}> +Обновляет ресурс. Повторное обращение к данному методу должно приводить к одному и +томуже результату. -Обновляет дочерний ресурс с указанным идентификатором. +=head3 C<DELETE> -=head3 C<DELETE {id}> - -Удаляет дочерний ресурс с указанным идентификатором. +Удаляет ресурс. =head3 C<POST> -Добавляет новый дочерний ресурс в коллекцию. - -=head3 C<POST {method}> - -Вызывает метод C<method>. - -=head2 HTTP METHOD MAPPING - -=head3 C<POST {method}> - -Вызывает метод C<method>, в отличии от C<GET> методы опубликованные через C<POST> могут вносить -изменения в объекты. +Данный метод может вести себя как угодно, однако обычно он используется для добавления +нового дочернего ресурса в коллекцию,также может использоваться для вызова метода, в случае +если происходит публикация методов в качестве дочерних ресурсов. =head1 BROWSER COMPATIBILITY @@ -251,29 +318,106 @@ C<GET,POST>. Для решения данной проблемы используется режим совместимости C<enableForms>. В случае когда данный режим активен, автоматически публикуются дочерние ресурсы C<create,edit,delete>. -=head2 C<GET create> +Данные ресуры пбликуются как методы, что означает то, что обращения к ним будут превращены в +выполнения соответсвующих методов на родительском объекте. + +=head2 C<create> + +По сути данные ресурс не является необходимостью, однако создается для целостности модели. + +=head3 C<GET> + +Передает управление методу C<get> -Возвращает C<target>. +=head3 C<POST> + +Передает управление методу C<post> + +=head2 C<edit> -=head2 C<POST create> +=head3 C<GET> + +Передает управление методу C<get> + +=head3 C<POST> -Вызывает метод C<PostImpl> передавая ему свои параметры. +Передает управление методу C<put>, как если бы он был выполнен непосредственно у +родительского ресурса. + +=head2 C<delete> + +=head3 C<GET> + +Передает управление методу C<get> + +=head3 C<POST> -=head2 C<GET edit> +Передает управление методу C<delete>, , как если бы он был выполнен непосредственно у +родительского ресурса. + +=head1 METHOD DEFINITIONS -Возвращает C<target>. +Все методы ресурсов данного типа задаются описаниями, хранящимися в соответствующих +свойствах. Когда наступает необходимость вызова соответствующего метода, его описание +бедется из свойства и передается методу C<InvokeMember>, который и производит вызов. + +=head2 C<HASH> + +Содержит в себе описание метода, который нужно вызвать, а также его параметры. + +=over -=head2 C<POST edit> +=item C<method> + +Имя метода который будет вызван. + +=item C<paremeters> + +Описание параметров метода, может быть либо массивом, либо хешем, либо простым +значением. + +=over + +=item C<ARRAY> -Вызывает метод C<$this->parent->PutImpl($this->id)> передавая ему свои параметры. +Метод получает список параметров, каждый элемент данного массива будет превращен +в параметр при помощи метода C<MakeParameter> + +=item C<HASH> -=head2 C<GET delete>. +Метод получает список параметров, который состоит пар ключ-значение, каждое значение +данного хеша будет превращено в зачение параметра метода при помощи метода C<MakeParameter>. +Ключи хеша изменениям не подвергаются. + +=item Простое значение + +Метод получает одно значение, которое будет получено из текущего при помощи C<MakeParameter>. -Возвращает C<target>. +=back + +=back + +=head2 C<CODE> + +Если в описании метода находится ссылка на функцию, то эта функция будет вызвана с параметрами. +Данный вариант полезен когда ресурсы создаются програмно обычного механизма описаний не достаточно +для реализации требуемого функционала. + +=over -=head2 C<POST delete>. +=item C<$resource> + +Текущий ресурс у которого производится вызов метода. + +=item C<$action> -Вызывает метод C<$this->parent->DeleteImpl($this->id)> передавая ему свои параметры. +Текущий запрос C<IMPL::Web::Application::Action>. + +=back + +=head2 Простое значение + +Интерпретируется как имя метода у объекта данных текущего ресурса. =head1 MEMBERS @@ -283,7 +427,8 @@ =head2 C<[get]target> -Объект (также может быть и класс), обеспечивающий функционал ресурса. +Объект данных (может быть и класс, поскольку у него будут только вызываться +методы), обеспечивающий функционал ресурса. =head2 C<[get]parent> @@ -305,34 +450,30 @@ отсутствует, то дочерние ресурсы не получится адресовать относительно данного. По умолчанию получает идентификатор дочернего ресурса первым параметром. -=head2 C<[get]list> +=head2 C<[get]index> Описание метода для получения списка дочерних объектов. По умолчанию не получает параметров. -=head2 C<[get]insert> +=head2 C<[get]post> Описание метода для добавление дочернего ресурса. По умолчанию получает объект C<CGI> описывабщий текущий запрос первым параметром. -=head2 C<[get]update> +=head2 C<[get]put> Описание метода для обновления дочернего ресурса. По умолчанию получает -идентификатор дочернего ресурса и объект C<CGI> текущего запроса. +объект C<CGI> текущего запроса. =head2 C<[get]delete> -Описание метода для удаления дочернего ресурса. По умолчанию получает -идентификатор дочернего ресурса. +Описание метода для удаления дочернего ресурса. По умолчанию не получает +параметров. -=head2 C<GetImpl($child,$action)> +=head2 C<GetImpl($action)> =over -=item C<$child> - -Идентификатор дочернего ресутсра - =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. @@ -342,14 +483,10 @@ Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. -=head2 C<PutImpl($child,$action)> +=head2 C<PutImpl($action)> =over -=item C<$child> - -Идентификатор дочернего ресутсра - =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. @@ -359,14 +496,10 @@ Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. -=head2 C<PostImpl($child,$action)> +=head2 C<PostImpl($action)> =over -=item C<$child> - -Идентификатор дочернего ресутсра - =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. @@ -376,14 +509,10 @@ Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. -=head2 C<DeleteImpl($child,$action)> +=head2 C<DeleteImpl($action)> =over -=item C<$child> - -Идентификатор дочернего ресутсра - =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. @@ -393,7 +522,7 @@ Переадресует запрос нужному методу внутреннего объекта C<target> при помощи C<InvokeMember>. -=head2 C<InvokeMember($memberInfo,$child,$action)> +=head2 C<InvokeMember($memberInfo,$action,$params)> =over @@ -401,20 +530,20 @@ Описание члена внутреннего объекта C<target>, который нужно вызвать. -=item C<$child> - -Идентификатор дочернего ресутсра - =item C<$action> Текущий запрос C<IMPL::Web::Application::Action>. +=item C<$params> + +Ссылка на хеш с предопределенными параметрами. + =back Вызывает метод внутреннего объекта C<target>, предварительно подготовив параметры на основе описания C<$memberInfo> и при помощи С<MakeParameter()>. -=head2 C<MakeParameter($paramDef,$child,$action)> +=head2 C<MakeParameter($paramDef,$action)> =over @@ -428,7 +557,7 @@ =item C<id> -Идентификатор дочернего ресурса +Идентификатор ресурса =item C<query> @@ -447,6 +576,10 @@ Если описание параметра - объект C<IMPL::Transform>, то будет выполнено это преобразование над C<CGI> объектом текущего запроса C<< $paramDef->Transform($action->query) >>. +=item C<$action> + +Текущий запрос + =back =cut \ No newline at end of file
--- a/Lib/IMPL/Web/Handler/RestController.pm Tue Apr 24 19:52:07 2012 +0400 +++ b/Lib/IMPL/Web/Handler/RestController.pm Wed Apr 25 02:49:23 2012 +0400 @@ -47,6 +47,7 @@ shift @segments; my ($obj,$view) = (pop(@segments) =~ m/(.*?)(?:\.(\w+))?$/); + push @segments, $obj; if ($this->types and my $type = $this->types->{$view}) { $action->response->contentType($type); @@ -56,12 +57,13 @@ while(@segments) { my $id = shift @segments; - $res = $this->contract->Transform( $res->InvokeHttpMethod('GET',$id,$action), { parent => $res, id => $id } ); + + $res = $res->FetchChildResource($id,$action); die NotFoundException->new() unless $res; } - $res = $res->InvokeHttpMethod($method,$obj,$action); + $res = $res->InvokeHttpMethod($method,$action); } 1;