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;