# HG changeset patch # User cin # Date 1371075897 -14400 # Node ID 71221d79e6b4db89f418b665f75c6fb8c90d0262 # Parent cd6409f66a5fa63196eabb31667769d9bbc4d5df removing web resources contracts diff -r cd6409f66a5f -r 71221d79e6b4 Lib/IMPL/Web/Application/CustomResource.pm --- 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 diff -r cd6409f66a5f -r 71221d79e6b4 Lib/IMPL/Web/Application/CustomResourceContract.pm --- 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 - контракт для веб-ресурсов, -реальзуемых в коде см. C. - -=head1 DESCRIPTION - -Данный класс не используется напрямую. - -=cut diff -r cd6409f66a5f -r 71221d79e6b4 Lib/IMPL/Web/Application/Resource.pm --- 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 { diff -r cd6409f66a5f -r 71221d79e6b4 Lib/IMPL/Web/Handler/RestController.pm --- 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 @@ - My::App::Web::RootResource" + My::App::Web::RootResource diff -r cd6409f66a5f -r 71221d79e6b4 Lib/IMPL/Web/Handler/TTView.pm --- 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; diff -r cd6409f66a5f -r 71221d79e6b4 Lib/IMPL/Web/View/TTControl.pm --- 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); } }