changeset 334:71221d79e6b4

removing web resources contracts
author cin
date Thu, 13 Jun 2013 02:24:57 +0400
parents cd6409f66a5f
children e8be9062ecf2
files Lib/IMPL/Web/Application/CustomResource.pm Lib/IMPL/Web/Application/CustomResourceContract.pm Lib/IMPL/Web/Application/Resource.pm Lib/IMPL/Web/Handler/RestController.pm Lib/IMPL/Web/Handler/TTView.pm Lib/IMPL/Web/View/TTControl.pm
diffstat 6 files changed, 177 insertions(+), 135 deletions(-) [+]
line wrap: on
line diff
--- a/Lib/IMPL/Web/Application/CustomResource.pm	Tue Jun 11 20:22:52 2013 +0400
+++ b/Lib/IMPL/Web/Application/CustomResource.pm	Thu Jun 13 02:24:57 2013 +0400
@@ -5,8 +5,8 @@
 
 use IMPL::declare {
     require => {
-        Factory => 'IMPL::Object::Factory',
-        CustomResourceContract => 'IMPL::Web::Application::CustomResourceContract'        
+        NotAllowedException => 'IMPL::Web::NotAllowedException',
+        HttpResponse => 'IMPL::Web::HttpResponse'
     },
     base => [
         'IMPL::Web::Application::Resource' => '@_'
@@ -16,49 +16,79 @@
     ]
 };
 
-__PACKAGE__->static_accessor(contractFactory => CustomResourceContract );
-__PACKAGE__->static_accessor_own(_contractInstance => undef);
+our %RESOURCE_BINDINGS = (
+    GET => 'HttpGet',
+    POST => 'HttpPost',
+    PUT => 'HttpPut',
+    DELETE => 'HttpDelete',
+    HEAD => 'HttpHead'
+);
+
+__PACKAGE__->static_accessor(_rxResourcesMap => undef, 'own');
+__PACKAGE__->static_accessor(_nameResourcesMap => undef, 'own');
+
+sub namedResources {
+    shift->_nameResourcesMap;
+}
+
+sub regexResources {
+    shift->_rxResourcesMap;
+}
 
 sub CTOR {
 	my ($this,%args) = @_;
 	
 	$this->accessCheck($args{accessCheck})
 	   if $args{accessCheck};
+	   
+	$this->verbs->{options} ||= \&_HttpOptionsBinding;
+    
+    while(my ($verb,$methodName) = each %RESOURCE_BINDINGS) {
+        $this->verbs->{lc($verb)} ||= sub {
+            my ($resource,$action) = @_;
+   
+            if (eval { $resource->can($methodName) }) {
+                return $resource->$methodName($action);
+            } else {
+                die NotAllowedException->new(allow => join(',', _GetAllowedHttpMethods($resource)));
+            }
+        }
+    }
 }
 
-sub contractInstance {
-    my ($self) = @_;
+sub FindChildResourceInfo {
+    my ( $this, $name ) = @_;
     
-    $self = ref $self || $self;
-    $self->_contractInstance ? $self->_contractInstance : $self->InitContract();
+    $this->_PrepareResourcesCache()
+        unless($this->_nameResourcesMap);
+        
+    return $this->next::method($name);
 }
 
-sub InitContract {
-    my ($self) = @_;
-    $self->_contractInstance(
-        $self->contractFactory->new(
-            resourceFactory => $self,
-            resources => [ $self->GetChildResources() ]
-        )
-    ); 
+sub PrepareResourcesCache {
+    # suppress default caching mechanisn
 }
 
-sub CreateContract {
-    my ($self, %args) = @_;
-    
-    $self->contractFactory->new(
-        resourceFactory => Factory->new(
-            $self,
-            \%args
-        ),
-        resources => [ $self->GetChildResources() ]
-    )
-}
+sub _PrepareResourcesCache {
+    # a little bit wired
+    my ($self) = @_;
+    my %nameMap;
+    my @rxMap;
 
-sub CreateResource {
-    my ($self,%args) = @_;
-    
-    $self->CreateContract()->CreateResource(%args);
+    foreach my $res ($self->GetChildResources()) {
+        #skip resources without contract
+        next unless $res->{contract};
+        
+        if ( my $name = $res->{name} ) {
+            $nameMap{$name} = $res;
+        }
+        if ( $res->{match} ) {
+            push @rxMap,$res;
+        }
+    }
+
+    $self->_rxResourcesMap(\@rxMap);
+    $self->_nameResourcesMap(\%nameMap);
 }
 
 sub AccessCheck {
@@ -67,7 +97,7 @@
 	my $handler = $this->accessCheck; 
 	
 	if(ref($handler) eq 'CODE') {
-		&$handler($this,$verb);
+		return &$handler($this,$verb);
 	}
 }
 
@@ -75,6 +105,23 @@
     
 }
 
+sub _HttpOptionsBinding {
+    my ($this) = @_;
+    
+    my @allow = $this->_GetAllowedHttpMethods();
+    return HttpResponse->new(
+        status => '200 OK',
+        headers => {
+            allow => join ( ',', @allow )
+        }
+    );
+}
+
+sub _GetAllowedHttpMethods {
+    my ($this) = @_;
+    return grep $this->can($RESOURCE_BINDINGS{$_}), keys %RESOURCE_BINDINGS;
+}
+
 
 1;
 
@@ -111,18 +158,22 @@
     $this->model->update( $form->Bind($action) );
 }
 
-our %COMPONENTS = (
-	item => {
-		verbs => {
-			get => sub {
-				shift->model;
-			}
-		},
-		resources => [
-			edit => 
-		]
-	}
-);
+sub GetChildResources {
+    return {
+        name => 'create',
+        contract => {
+            class => 'My::Web::FormResource',
+            formName => 'create',
+            schema => 'profile.schema'
+        }
+    },
+    {
+        match => qr/^(.*)$/,
+        contract => {
+            class => 'My::Web::ItemResource'
+        }
+    }
+}
 
 =end code
 
--- a/Lib/IMPL/Web/Application/CustomResourceContract.pm	Tue Jun 11 20:22:52 2013 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-package IMPL::Web::Application::CustomResourceContract;
-use strict;
-
-use IMPL::Const qw(:prop);
-use IMPL::declare {
-    require => {
-        NotAllowedException => 'IMPL::Web::NotAllowedException'
-    },
-    base => [
-        'IMPL::Web::Application::ResourceContract' => '@_'
-    ]
-};
-
-our %RESOURCE_BINDINGS = (
-    GET => 'HttpGet',
-    POST => 'HttpPost',
-    PUT => 'HttpPut',
-    DELETE => 'HttpDelete',
-    HEAD => 'HttpHead'
-);
-
-sub CTOR {
-    my ($this) = @_;
-    
-    $this->verbs->{options} ||= \&_HttpOptionsBinding;
-    
-    while(my ($verb,$methodName) = each %RESOURCE_BINDINGS) {
-        $this->verbs->{lc($verb)} ||= sub {
-            my ($resource,$action) = @_;
-   
-            if (eval { $resource->can($methodName) }) {
-                return $resource->$methodName($action);
-            } else {
-                die NotAllowedException->new(allow => join(',', _GetAllowedHttpMethods($resource)));
-            }
-        }
-    }
-}
-
-sub _HttpOptionsBinding {
-    my ($resource) = @_;
-    
-    my @allow = _GetAllowedHttpMethods($resource);
-    retrun HttpResponse->new(
-        status => '200 OK',
-        headers => {
-            allow => join ( ',', @allow )
-        }
-    );
-}
-
-sub _GetAllowedHttpMethods {
-    my ($resource) = @_;
-    return grep $resource->can($RESOURCE_BINDINGS{$_}), keys %RESOURCE_BINDINGS;
-}
-
-1;
-
-__END__
-
-=pod
-
-=head1 NAME
-
-C<IMPL::Web::Application::CustomResourceContract> - контракт для веб-ресурсов,
-реальзуемых в коде см. C<IMPL::Web::Application::CustomResource}>.
-
-=head1 DESCRIPTION
-
-Данный класс не используется напрямую.
-
-=cut
--- a/Lib/IMPL/Web/Application/Resource.pm	Tue Jun 11 20:22:52 2013 +0400
+++ b/Lib/IMPL/Web/Application/Resource.pm	Thu Jun 13 02:24:57 2013 +0400
@@ -2,7 +2,8 @@
 use strict;
 
 use URI;
-use Carp qw(carp); 
+use Carp qw(carp);
+use IMPL::lang qw(:hash :base);
 use IMPL::Const qw(:prop);
 use IMPL::declare {
 	require => {
@@ -11,7 +12,8 @@
 		ArgumentException   => '-IMPL::InvalidArgumentException',
 		OperationException  => '-IMPL::InvalidOperationException',
 		NotAllowedException => 'IMPL::Web::NotAllowedException',
-		NotFoundException   => 'IMPL::Web::NotFoundException'
+		NotFoundException   => 'IMPL::Web::NotFoundException',
+		Loader              => 'IMPL::Code::Loader' 
 	  },
 	  base => [
 		'IMPL::Object'                              => undef,
@@ -23,8 +25,11 @@
 		parent      => PROP_RO,
 		model       => PROP_RO,
 		id          => PROP_RO,
-		contract    => PROP_RO,
 		location    => PROP_RO,
+		resources   => PROP_RO,
+		verbs       => PROP_RO,
+		namedResources => PROP_RO,
+		regexResources => PROP_RO
 	  ]
 };
 
@@ -33,16 +38,20 @@
 
 	die ArgumentException->new( id => 'A resource identifier is required' )
 	  unless $args{id};
-	die ArgumentException->new( contract => 'A contract is required' )
-	  unless $args{contract};
 
-	$this->request($args{request})
-		or die ArgumentException->new(request => 'A request object must be specified');
+	
+    die ArgumentException->new(request => 'A request object must be specified')
+        unless $args{request};
+	
+	$this->request( $args{request} );	
 	$this->parent( $args{parent} );
 	$this->model( $args{model} );
 	$this->id( $args{id} );
-	$this->contract( $args{contract} );
 	$this->application( $args{request}->application );
+	$this->verbs( $args{verbs} || {} );
+	$this->resources($args{resources} || []);
+	
+	$this->PrepareResourcesCache();
 
 # если расположение явно не указано, то оно вычисляется автоматически,
 # либо остается не заданным
@@ -50,13 +59,34 @@
 		  || eval { $this->parent->location->Child( $this->id ) } );
 }
 
+sub PrepareResourcesCache {
+    my ($this,$resources) = @_;
+    my %nameMap;
+    my @rxMap;
+
+    foreach my $res (@{$this->resources}) {
+        #skip resources without contract
+        next unless $res->{contract};
+        
+        if ( my $name = $res->{name} ) {
+            $nameMap{$name} = $res;
+        }
+        if ( $res->{match} ) {
+            push @rxMap,$res;
+        }
+    }
+
+    $this->regexResources(\@rxMap);
+    $this->namedResources(\%nameMap);
+}
+
 sub InvokeHttpVerb {
 	my ( $this, $verb ) = @_;
 
-	my $operation = $this->contract->verbs->{ lc($verb) };
+	my $operation = $this->verbs->{ lc($verb) };
 
 	die NotAllowedException->new(
-		allow => join( ',', map( uc, keys %{ $this->contract->verbs } ) ) )
+		allow => join( ',', map( uc, keys %{ $this->verbs } ) ) )
 	  unless $operation;
 
 	$this->AccessCheck($verb);
@@ -100,8 +130,21 @@
 }
 
 sub FindChildResourceInfo {
-	my ($this,$resourceId) = @_;
-	return $this->contract->FindChildResourceInfo($resourceId);
+    my ( $this, $name ) = @_;
+    
+    if ( my $info = $this->namedResources->{$name} ) {
+        return $info, [$name];
+    }
+    else {
+        foreach my $info ( @{$this->regexResources} ) {
+            my $rx = $info->{match};
+            if(my @childId = $name =~ m/$rx/) {
+                return $info, \@childId; 
+            }
+        }
+    }
+
+    return;
 }
 
 # это реализация по умолчанию, базируется информации о ресурсах, содержащийся
@@ -127,9 +170,9 @@
 		$args{model} = _InvokeDelegate( $binding, $this, @$childIdParts );
 	}
 
-	if ( ref $contract eq 'CODE' || $contract->can('Invoke') ) {
+    # support for dynamic contracts
+	if ( ref $contract eq 'CODE' || eval { $contract->can('Invoke') } ) {
 		$contract = _InvokeDelegate( $contract, $this, $args{model} );
-		$info->{contract} = $contract;
 	}
 
 	die OperationException->new( "Can't fetch a contract for the resource",
@@ -139,8 +182,20 @@
 	$args{parent} = $this;
 	$args{id}     = $childId;
 	$args{request} = $this->request;
+	
+	my $factory;
+	
+	if (ref($contract) eq 'HASH') {
+	    $factory = delete $contract->{class} || __PACKAGE__;
+	    hashApply(\%args,$contract);
+	    
+	    Loader->default->Require($factory)
+	       unless ref($factory);
+	} else {
+	    die OperationException->new("Unsupported contract for the child resource '$childId'",$contract,$this->location);
+	}
 
-	return $contract->CreateResource(%args);
+	return $factory->new(%args);
 }
 
 sub _InvokeDelegate {
--- a/Lib/IMPL/Web/Handler/RestController.pm	Tue Jun 11 20:22:52 2013 +0400
+++ b/Lib/IMPL/Web/Handler/RestController.pm	Thu Jun 13 02:24:57 2013 +0400
@@ -89,7 +89,7 @@
 
     <handlers type="ARRAY">
         <item type="IMPL::Web::Handler::RestController">
-            <resourceFactory>My::App::Web::RootResource"</resourceFactory>
+            <resourceFactory>My::App::Web::RootResource</resourceFactory>
         </item>
         <item type="IMPL::Web::Handler::JSONView" />
         <item type="IMPL::Web::Handler::SecureCookie" />
--- a/Lib/IMPL/Web/Handler/TTView.pm	Tue Jun 11 20:22:52 2013 +0400
+++ b/Lib/IMPL/Web/Handler/TTView.pm	Thu Jun 13 02:24:57 2013 +0400
@@ -51,8 +51,9 @@
     my $vars = {
         view        => $view,
         model       => $model,
-        action      => $action,
+        action      => sub { carp "action variable is deprecated"; $action },
         app         => $action->application,
+        context     => $action->context,
         env         => _cached($action->context->{environment}),
         ImportClass => sub {
             my $class = shift;
--- a/Lib/IMPL/Web/View/TTControl.pm	Tue Jun 11 20:22:52 2013 +0400
+++ b/Lib/IMPL/Web/View/TTControl.pm	Thu Jun 13 02:24:57 2013 +0400
@@ -2,7 +2,7 @@
 use strict;
 
 use IMPL::Const qw(:prop);
-use IMPL::lang qw(:hash);
+use IMPL::lang qw(:hash :base);
 use Scalar::Util qw(blessed reftype);
 use IMPL::declare {
 	require => {
@@ -33,17 +33,24 @@
 
 our $AUTOLOAD_REGEX = qr/^[a-z]/;
 
+my %mapSkipAttributes = map { $_, 1 } qw(attributes context);
+
 sub CTOR {
     my ($this,$template,$context,$attrs) = @_;
     
    
     $this->template( $template ) or die new IMPL::ArgumentException("A template is required");
-    $this->context( $context ) or die new IMPL::ArgumentException("A context is required");
+    
+    die IMPL::ArgumentException->new(context => "A context is required, supplied: $context")
+        unless is($context,TTContext);
+    
+    $this->context( $context );
     
     $this->attributes({});
     
     if(ref($attrs) eq 'HASH') {
 	    while (my($key,$value) = each %$attrs) {
+	        next if $mapSkipAttributes{$key};
 	        $this->SetAttribute($key,$value);
 	    }
     }