diff Lib/IMPL/Web/Application/RestResource.pm @ 199:e743a8481327

Added REST support for forms (with only get and post methods)
author sergey
date Mon, 23 Apr 2012 01:36:52 +0400
parents 2ffe6f661605
children a9dbe534d236
line wrap: on
line diff
--- a/Lib/IMPL/Web/Application/RestResource.pm	Fri Apr 20 16:06:36 2012 +0400
+++ b/Lib/IMPL/Web/Application/RestResource.pm	Mon Apr 23 01:36:52 2012 +0400
@@ -9,7 +9,8 @@
 		ForbiddenException => 'IMPL::Web::ForbiddenException',
 		InvalidOpException => '-IMPL::InvalidOperationException',
 		ArgumentException => '-IMPL::InvalidArgumentException',
-		TTransform => '-IMPL::Transform'
+		TTransform => '-IMPL::Transform',
+		TResolve => '-IMPL::Config::Resolve'
 	},
 	base => {
 		'IMPL::Object' => undef,
@@ -18,9 +19,12 @@
 };
 
 BEGIN {
+	public property id => PROP_GET | PROP_OWNERSET;
 	public property target => PROP_GET | PROP_OWNERSET;
+	public property parent => 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;
 	public property list => PROP_GET | PROP_OWNERSET;
 	public property fetch => PROP_GET | PROP_OWNERSET;
 	public property insert => PROP_GET | PROP_OWNERSET;
@@ -31,7 +35,47 @@
 sub CTOR {
 	my ($this) = @_;
 	
+	die ArgumentException->new("id","Identifier is required for non-root resources") if $this->id and not length $this->id;
 	die ArgumentException->new("target") unless $this->target;
+	
+	if ($this->enableForms && $this->parent) {
+		$this->methods({}) unless $this->methods;
+		
+		if ($this->insert) {
+			$this->methods->{create} = {
+				get => sub {
+					my ($that,$id,$action) = @_;
+					return $that->target;
+		        }
+			};
+		}
+		
+		if ($this->parent->update) {
+			$this->methods->{edit} = {
+                get => sub {
+                    my ($that,$id,$action) = @_;
+                    return $that->target;
+                },
+                post => sub {
+                	my ($that,$id,$action) = @_;
+                	return $that->parent->PutImpl($that->id,$action);
+                } 
+            };
+		}
+		
+		if ($this->parent->delete) {
+            $this->methods->{delete} = {
+                get => sub {
+                    my ($that,$id,$action) = @_;
+                    return $that->target;
+                },
+                post => sub {
+                    my ($that,$id,$action) = @_;
+                    return $that->parent->DeleteImpl($that->id,$action);
+                } 
+            };
+        }
+	}
 }
 
 sub GetHttpImpl {
@@ -62,10 +106,8 @@
     my $method;
     if (length $id == 0) {
     	$method = $this->list or die ForbiddenException->new();
-    } elsif ($this->methods and $method = $this->methods->{$id}) {
-    	if (ref $method eq 'HASH' and not $method->{allowGet}) {
-    		die ForbiddenException->new();
-    	}
+    } elsif ($this->methods and $method = $this->methods->{$id}->{get}) {
+    	# we got method info
     } elsif($rx = $this->childRegex and $id =~ m/$rx/ ) {
     	$method = $this->fetch or die ForbiddenException->new();
         
@@ -74,6 +116,8 @@
         	parameters => [qw(id)]
         } unless ref $method;
         
+    } else {    
+        die ForbiddenException->new();
     }
     
     return $this->InvokeMember($method,$id,$action);
@@ -109,8 +153,8 @@
 			method => $method,
 			parameters => [qw(query)]
 		} unless ref $method;
-	} elsif ($method = $this->methods->{$id}) {
-		die ForbiddenException->new() unless ref $method and $method->{allowPost}; 
+	} elsif ($this->methods and $method = $this->methods->{$id}->{post}) {
+		# we got method info 
 	} else {
 		die ForbiddenException->new();
 	}
@@ -142,6 +186,8 @@
 sub InvokeMember {
 	my ($this,$method,$id,$action) = @_;
 	
+	die ArgumentException->new("method","No method information provided") unless $method;
+	
 	#normalize method info
 	if (not ref $method) {
 		$method = {
@@ -150,21 +196,26 @@
 	}
 	
 	if (ref $method eq 'HASH') {
+		my $member = $method->{method} or die InvalidOpException->new("A member name isn't specified");
 		my @args;
-		my $member = $method->{method} or die InvalidOpException->new("A member name isn't specified");
-		if (my $params = $method->{parameters}) {
-			if (ref $params eq 'HASH') {
-				@args = map {
-					$_,
-					$this->MakeParameter($params->{$_},$id,$action)
-				} keys %$params;				
-			} elsif (ref $params eq 'ARRAY') {
-				@args = map $this->MakeParameter($_,$id,$action), @$params;
-			} else {
-				@args = ($this->MakeParameter($params,$id,$action)); 
-			}
-		}
-		$this->target->$member(@args);
+    
+	    if (my $params = $method->{parameters}) {
+	        if (ref $params eq 'HASH') {
+	            @args = map {
+	                $_,
+	                $this->MakeParameter($params->{$_},$id,$action)
+	            } keys %$params;                
+	        } elsif (ref $params eq 'ARRAY') {
+	            @args = map $this->MakeParameter($_,$id,$action), @$params;
+	        } else {
+	            @args = ($this->MakeParameter($params,$id,$action)); 
+	        }
+	    }
+		return $this->target->$member(@args);
+	} elsif (ref $method eq TResolve) {
+		return $method->Invoke($this->target);
+	} elsif (ref $method eq 'CODE') {
+		return $method->($this,$id,$action);
 	} else {
 		die InvalidOpException->new("Unsupported type of the method information", ref $method);
 	}
@@ -255,10 +306,20 @@
     {
     	methods => {
     		history => {
-    			allowGet => 1,
-    			method => 'GetHistory',
-    			parameters => [qw(from to)] 
+    			get => {
+	    			method => 'GetHistory',
+	    			parameters => [qw(from to)]
+    			}, 
     		},
+    		rating => {
+    			get => {
+    				method => 'GetRating'
+    			}
+    			post => {
+    				method => 'Vote',
+    				parameters => [qw(id rating comment)]
+    			}
+    		}
     	}
     	list => 'search',
     	fetch => 'GetItemById'
@@ -309,12 +370,51 @@
 Вызывает метод C<method>, в отличии от C<GET> методы опубликованные через C<POST> могут вносить
 изменения в объекты. 
 
+=head1 BROWSER COMPATIBILITY
+
+Однако существует проблема с браузерами, поскольку тег C<< <form> >> реализет только методы
+C<GET,POST>. Для решения данной проблемы используется режим совместимости C<compatible>. В
+случае когда данный режим активен, автоматически публикуются дочерние C<create,edit,delete>.
+
+=head2 C<GET create>
+
+Возвращает C<target>.
+
+=head2 C<POST create>
+
+Вызывает метод C<PostImpl> передавая ему свои параметры.
+
+=head2 C<GET edit>
+
+Возвращает C<target>.
+
+=head2 C<POST edit>
+
+Вызывает метод C<$this->parent->PutImpl($this->id)> передавая ему свои параметры.
+
+=head2 C<GET delete>.
+
+Возвращает C<target>.
+
+=head2 C<POST delete>.
+
+Вызывает метод C<$this->parent->DeleteImpl($this->id)> передавая ему свои параметры.
+
 =head1 MEMBERS
 
+=head2 C<[get]id>
+
+Идентификатор текущего ресурса.
+
 =head2 C<[get]target>
 
 Объект (также может быть и класс), обеспечивающий функционал ресурса.
 
+=head2 C<[get]parent>
+
+Родительский ресурс, в котором находится текущий ресурс. Может быть C<undef>,
+если текущий ресурс является корнем.
+
 =head2 C<[get]methods>
 
 Содержит описания методов, которые будут публиковаться как дочерние ресурсы.