# HG changeset patch # User cin # Date 1387900915 -14400 # Node ID e12c1417784883738542b2ebe6414115099a7d9a # Parent d5c8b955bf8d0d7dc20ead90bb3b34d61a890dd6 refactoring web resources model, implementing new simplified model diff -r d5c8b955bf8d -r e12c14177848 Lib/IMPL/Web/Application/CustomResource.pm --- a/Lib/IMPL/Web/Application/CustomResource.pm Fri Dec 13 16:49:47 2013 +0400 +++ b/Lib/IMPL/Web/Application/CustomResource.pm Tue Dec 24 20:01:55 2013 +0400 @@ -2,123 +2,184 @@ use strict; use IMPL::Const qw(:prop); - +use IMPL::lang qw(:hash :base); use IMPL::declare { - require => { - NotAllowedException => 'IMPL::Web::NotAllowedException', - HttpResponse => 'IMPL::Web::HttpResponse' - }, - base => [ - 'IMPL::Web::Application::Resource' => '@_' - ], - props => [ - accessCheck => PROP_RW, - resources => PROP_RO, - verbs => PROP_RO, - namedResources => PROP_RO, - regexResources => PROP_RO - ] + require => { + Exception => 'IMPL::Exception', + OperationException => '-IMPL::InvalidOperationException', + NotFoundException => 'IMPL::Web::NotFoundException', + HttpResponse => 'IMPL::Web::HttpResponse', + Loader => 'IMPL::Code::Loader' + }, + base => [ 'IMPL::Web::Application::ResourceBase' => '@_' ], + props => [ + accessCheck => PROP_RW, + resources => PROP_RO, + verbs => PROP_RO, + namedResources => PROP_RO, + regexResources => PROP_RO + ] }; +use constant { CustomResource => __PACKAGE__ }; + our %RESOURCE_BINDINGS = ( - GET => 'HttpGet', - POST => 'HttpPost', - PUT => 'HttpPut', - DELETE => 'HttpDelete', - HEAD => 'HttpHead', - OPTIONS => 'HttpOptions', - TRACE => 'HttpTrace' + GET => 'HttpGet', + POST => 'HttpPost', + PUT => 'HttpPut', + DELETE => 'HttpDelete', + HEAD => 'HttpHead', + OPTIONS => 'HttpOptions', + TRACE => 'HttpTrace' ); sub CTOR { - my ($this,%args) = @_; - - $this->verbs($args{verbs} || {}); - $this->resources($args{resources} || []); - - $this->accessCheck($args{accessCheck}) - if $args{accessCheck}; - - while(my ($verb,$methodName) = each %RESOURCE_BINDINGS) { - if(my $method = $this->can($methodName)) { - $this->verbs->{lc($verb)} ||= $method; - } - } + my ( $this, %args ) = @_; + + $this->verbs( $args{verbs} || {} ); + $this->resources( $args{resources} || [] ); + + $this->accessCheck( $args{accessCheck} ) + if $args{accessCheck}; + + while ( my ( $verb, $methodName ) = each %RESOURCE_BINDINGS ) { + if ( my $method = $this->can($methodName) ) { + $this->verbs->{ lc($verb) } ||= $method; + } + } } sub FindChildResourceInfo { - my ( $this, $name ) = @_; - - $this->PrepareResourcesCache() - unless $this->namedResources; - - 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; - } - } - } + my ( $this, $name ) = @_; + + $this->PrepareResourcesCache() + unless $this->namedResources; - return; + 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; } -sub GetAllowedMethods { - map( uc, keys %{ shift->verbs } ); +# это реализация по умолчанию, базируется информации о ресурсах, содержащийся +# в контракте. +sub FetchChildResource { + my ( $this, $childId ) = @_; + + $this->AccessCheck('FETCH'); + + my ( $info, $childIdParts ) = $this->FindChildResourceInfo($childId); + + die NotFoundException->new( $this->location->url, $childId ) unless $info; + + my %args; + + my $binding = $info->{binding}; + my $contract = $info->{contract}; + if ( ref($binding) eq 'HASH' ) { + $args{$_} = _InvokeDelegate( $binding->{$_}, $this, @$childIdParts ) + foreach keys %$binding; + } + else { + $args{model} = _InvokeDelegate( $binding, $this, @$childIdParts ); + } + + # support for dynamic contracts + if ( ref $contract eq 'CODE' || eval { $contract->can('Invoke') } ) { + $contract = _InvokeDelegate( $contract, $this, $args{model} ); + } + + die OperationException->new( "Can't fetch a contract for the resource", + $childId ) + unless $contract; + + $args{parent} = $this; + $args{id} = $childId; + $args{request} = $this->request; + + my $factory; + + if ( ref($contract) eq 'HASH' ) { + $factory = delete $contract->{class} || CustomResource; + 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 $factory->new(%args); } sub PrepareResourcesCache { - my ($this) = @_; - - my @resources = ($this->GetChildResources(), @{$this->resources}); - - my %nameMap; - my @rxMap; + my ($this) = @_; + + my @resources = ( $this->GetChildResources(), @{ $this->resources } ); + + my %nameMap; + my @rxMap; + + foreach my $res (@resources) { - foreach my $res (@resources) { - #skip resources without contract - next unless $res->{contract}; - - if ( my $name = $res->{name} ) { - $nameMap{$name} = $res; - } - if ( $res->{match} ) { - push @rxMap,$res; - } - } + #skip resources without contract + next unless $res->{contract}; - $this->regexResources(\@rxMap); - $this->namedResources(\%nameMap); + if ( my $name = $res->{name} ) { + $nameMap{$name} = $res; + } + if ( $res->{match} ) { + push @rxMap, $res; + } + } + + $this->regexResources( \@rxMap ); + $this->namedResources( \%nameMap ); } sub AccessCheck { - my ($this,$verb) = @_; - - my $handler = $this->accessCheck; - - if(ref($handler) eq 'CODE') { - return &$handler($this,$verb); + my ( $this, $verb ) = @_; + + my $handler = $this->accessCheck; + + if ( ref($handler) eq 'CODE' ) { + return &$handler( $this, $verb ); } } sub GetChildResources { - + } sub HttpOptions { - my ($this) = @_; - - my @allow = $this->GetAllowedMethods(); - return HttpResponse->new( - status => '200 OK', - headers => { - allow => join ( ',', @allow ) - } - ); + my ($this) = @_; + + my @allow = $this->GetAllowedMethods(); + return HttpResponse->new( + status => '200 OK', + headers => { + allow => join( ',', @allow ) + } + ); +} + +sub _InvokeDelegate { + my $delegate = shift; + + return $delegate->(@_) if ref $delegate eq 'CODE'; + return $delegate->Invoke(@_) if eval { $delegate->can('Invoke') }; } 1; @@ -227,4 +288,4 @@ Метод возвращает список из хешей, которые будут переданы в качестве параметра C контракту данного ресурса. -=cut \ No newline at end of file +=cut diff -r d5c8b955bf8d -r e12c14177848 Lib/IMPL/Web/Application/Resource.pm --- a/Lib/IMPL/Web/Application/Resource.pm Fri Dec 13 16:49:47 2013 +0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,323 +0,0 @@ -package IMPL::Web::Application::Resource; -use strict; - -use URI; -use Carp qw(carp); -use IMPL::lang qw(:hash :base); -use IMPL::Const qw(:prop); -use IMPL::declare { - require => { - ViewResult => 'IMPL::Web::ViewResult', - Exception => 'IMPL::Exception', - ArgumentException => '-IMPL::InvalidArgumentException', - OperationException => '-IMPL::InvalidOperationException', - NotAllowedException => 'IMPL::Web::NotAllowedException', - NotFoundException => 'IMPL::Web::NotFoundException', - Loader => 'IMPL::Code::Loader', - CustomResource => '-IMPL::Web::Application::CustomResource' - }, - base => [ - 'IMPL::Object' => undef, - 'IMPL::Web::Application::ResourceInterface' => undef - ], - props => [ - request => PROP_RO, - application => PROP_RO, - parent => PROP_RO, - model => PROP_RO, - id => PROP_RO, - location => PROP_RO, - role => PROP_RO | PROP_LIST - ] -}; - -sub CTOR { - my ( $this, %args ) = @_; - - die ArgumentException->new( id => 'A resource identifier is required' ) - unless $args{id}; - - - 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->application( $args{request}->application ); - -# если расположение явно не указано, то оно вычисляется автоматически, -# либо остается не заданным - $this->location( $args{location} - || eval { $this->parent->location->Child( $this->id ) } ); - - if (my $role = $args{role}) { - if (ref($role) eq 'ARRAY') { - $this->role($role); - } elsif (not ref($role)) { - $this->role(split(/\s+/, $role)); - } else { - die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); - } - } -} - -sub InvokeHttpVerb { - my ( $this, $verb ) = @_; - - my $operation = $this->verbs->{ lc($verb) }; - - die NotAllowedException->new( - allow => join( ',', $this->GetAllowedMethods ) ) - unless $operation; - - $this->AccessCheck($verb); - my $request = $this->request; - -# в случае, когда один ресурс вызывает HTTP метод другого ресурса, нужно -# сохранить оригинальный resourceLocation - $request->context->{resourceLocation} ||= $this->location; - -# это свойство специфично только для REST приложений. -# сохранение текущего ресурса не повлечет за собой существенных расходов, -# т.к. они просто освободятся несколько позже. - if ( not $request->context->{resource} ) { - $request->context->{resource} = $this; - $request->context->{environment} = sub { - carp "using request environment is deprecated"; - $this->PrepareEnvironment() - }; - } - - return _InvokeDelegate( $operation, $this, $request ); -} - -sub security { - shift->request->security -} - -sub verbs { - {} # возвращаем пстой список операций -} - -sub GetAllowedMethods { - # возвращаем пустой список доступных операций -} - -sub FindChildResourceInfo { - -} - -sub AccessCheck { - -} - -# это реализация по умолчанию, базируется информации о ресурсах, содержащийся -# в контракте. -sub FetchChildResource { - my ( $this, $childId ) = @_; - - $this->AccessCheck('FETCH'); - - my ( $info, $childIdParts ) = - $this->FindChildResourceInfo($childId); - - die NotFoundException->new( $this->location->url, $childId ) unless $info; - - my %args; - - my $binding = $info->{binding}; - my $contract = $info->{contract}; - if (ref($binding) eq 'HASH' ) { - $args{$_} = _InvokeDelegate( $binding->{$_}, $this, @$childIdParts ) - foreach keys %$binding; - } else { - $args{model} = _InvokeDelegate( $binding, $this, @$childIdParts ); - } - - # support for dynamic contracts - if ( ref $contract eq 'CODE' || eval { $contract->can('Invoke') } ) { - $contract = _InvokeDelegate( $contract, $this, $args{model} ); - } - - die OperationException->new( "Can't fetch a contract for the resource", - $childId ) - unless $contract; - - $args{parent} = $this; - $args{id} = $childId; - $args{request} = $this->request; - - my $factory; - - if (ref($contract) eq 'HASH') { - $factory = delete $contract->{class} || CustomResource; - 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 $factory->new(%args); -} - -sub _InvokeDelegate { - my $delegate = shift; - - return $delegate->(@_) if ref $delegate eq 'CODE'; - return $delegate->Invoke(@_) if eval { $delegate->can('Invoke') }; -} - -sub Seek { - my ($this, $role) = @_; - - my @roles; - - if (ref($role) eq 'ARRAY') { - @roles = @{$role}; - } elsif (not ref($role)) { - @roles = split(/\s+/, $role); - } else { - die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); - } - - - for(my $r = $this; $r; $r = $r->parent) { - return $r if $r->HasRole(@roles); - } - return; -} - -sub HasRole { - my ($this, @roles) = @_; - my %cache = map { $_, 1 } @{$this->role}; - return scalar(grep not($cache{$_}), @roles) ? 0 : 1; -} - -1; - -__END__ - -=pod - -=head1 NAME - -C - Web-ресурс. - -=head1 SYNOPSIS - -Класс для внутреннего использования. Объединяет в себе контракт и модель данных. -Основная задача - обработать поступающий от контроллера запрос на вызов C -метода. - -Экземпляры данного класса передаются в качестве параметров делегатам -осуществляющим привязку к модели в C -и C. - -=head1 DESCRIPTION - -Весь функционал ресурса, поддерживаемые им C методы определяются -контрактом. Однако можно реализовывать ресурсы, которые не имеют контракта -или он отличается от того, что предоставляется стандартно -C. - -Каждый ресурс является контейнером, тоесть позволяет получить дочерний ресурс -по идентифифкатору, если таковой имеется, тоесть ресурс, у которого нет дочерних -ресурсов на самом деле рассматривается как пустой контейнер. - -С ресурсом непосредственно взаимодействует котроллер запросов -C, вызывая два метода. - -=over - -=item * C - -Данный метод возвращает дочерний ресурс, соответствующий C<$childId>. -Текущая реализация использует метод C контракта текущего -ресурса, после чего создает дочерний ресурс. - -Если дочерний ресурс не найден, вызывается исключение -C. - -=item * C - -Обрабатывает запрос к ресурсу. Для этого используется контракт ресурса, в -нем выбирается соответсвующий C. -Затем найденный контракт для указанной операции используется для обработки -запроса. - -=back - -Если объект реализует два вышеуказанных метода, он является веб-ресурсом, а -детали его реализации, котнракт и прочее уже не важно, поэтому можно реализовать -собственный класс ресурса, например унаследованный от -C. - -=head1 MEMBERS - -=head2 C<[get]request> - -Объект C представляющий запрос к серверу. - -=head2 C<[get]application> - -Ссылка на приложение, к которому относится данный ресурс. Получается -автоматически из объекта запроса. - -=head2 C<[get]contract> - -Обязательное свойство для ресурса, ссылается, на контракт, соответствующий -данному ресурсу, используется для выполнения C методов и получения -дочерних ресурсов. - -=head2 C<[get]id> - -Обязательное свойство ресурса, идентифицирует его в родительском контейнере, -для корневого ресурса может иметь произвольное значение. - -=head2 C<[get]parent> - -Ссылка на родительский ресурс, для корневого ресурса не определена. - -=head2 C<[get]model> - -Ссылка на объект предметной области, представляемый данным ресурсом. Данное -свойство не является обязательным и может быть не задано. - -=head2 C<[get]location> - -Объект типа C или аналогичный описывающий адрес текущего -ресурса, может быть как явно передан при создании ресурса, так и вычислен -автоматически (только для ресурсов имеющих родителя). Следует заметить, что -адрес ресурса не содержит параметров запроса, а только путь. - -=head2 C<[get,list]role> - -Список ролей ресурса. Роль это условный маркер, который позволяет определить -функции выполняемые ресурсом, например контейнер, профиль пользователя и т.п. - -Используется при построении цепочек навигации, а также при поиске с использованием -метода C. - -=head2 C - -Ищет ресурс в цепочке родителей (включая сам ресурс) с подходящими ролями. - -Роли могут быть переданы в виде массива или строки, где роли разделены пробелами - -=head2 C<[get]FetchChildResource($id)> - -Возвращает дочерний ресурс, по его идентификатору. - -Данная реализация использует контракт текущего ресурса для поиска информации о -дочернем ресурсе C<< $this->contract->FindChildResourceInfo($id) >>. - -Затем осуществляется привязка к моделе, тоесть, выполняется делегат, для -получения модели дочернего ресурса, а затем осуществляется привязка к контракту, -при этом в делегат, который должен вернуть контракт дочернего ресурса передаются -текущий ресурc и модель дочернего ресурса. - -=cut diff -r d5c8b955bf8d -r e12c14177848 Lib/IMPL/Web/Application/ResourceBase.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/Web/Application/ResourceBase.pm Tue Dec 24 20:01:55 2013 +0400 @@ -0,0 +1,262 @@ +package IMPL::Web::Application::ResourceBase; +use strict; + +use URI; +use Carp qw(carp); +use IMPL::lang qw(:hash :base); +use IMPL::Const qw(:prop); +use IMPL::declare { + require => { + + Exception => 'IMPL::Exception', + ArgumentException => '-IMPL::InvalidArgumentException', + OperationException => '-IMPL::InvalidOperationException', + NotAllowedException => 'IMPL::Web::NotAllowedException', + + }, + base => [ + 'IMPL::Object' => undef, + 'IMPL::Web::Application::ResourceInterface' => undef + ], + props => [ + request => PROP_RO, + application => PROP_RO, + parent => PROP_RO, + model => PROP_RO, + id => PROP_RO, + location => PROP_RO, + role => PROP_RO | PROP_LIST + ] +}; + +sub CTOR { + my ( $this, %args ) = @_; + + 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->application( $args{request}->application ); + +# если расположение явно не указано, то оно вычисляется автоматически, +# либо остается не заданным + $this->location( $args{location} + || eval { $this->parent->location->Child( $this->id ) } ); + + if (my $role = $args{role}) { + if (ref($role) eq 'ARRAY') { + $this->role($role); + } elsif (not ref($role)) { + $this->role(split(/\s+/, $role)); + } else { + die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); + } + } +} + +sub InvokeHttpVerb { + my ( $this, $verb ) = @_; + + my $operation = $this->verbs->{ lc($verb) }; + + die NotAllowedException->new( + allow => join( ',', $this->GetAllowedMethods ) ) + unless $operation; + + $this->AccessCheck($verb); + my $request = $this->request; + +# в случае, когда один ресурс вызывает HTTP метод другого ресурса, нужно +# сохранить оригинальный resourceLocation + $request->context->{resourceLocation} ||= $this->location; + +# это свойство специфично только для REST приложений. +# сохранение текущего ресурса не повлечет за собой существенных расходов, +# т.к. они просто освободятся несколько позже. + if ( not $request->context->{resource} ) { + $request->context->{resource} = $this; + $request->context->{environment} = sub { + carp "using request environment is deprecated"; + $this->PrepareEnvironment() + }; + } + + return _InvokeDelegate( $operation, $this, $request ); +} + +sub security { + shift->request->security +} + +sub verbs { + {} # возвращаем пстой список операций +} + +sub GetAllowedMethods { + map( uc, keys %{ shift->verbs } ); +} + +sub AccessCheck { + +} + +sub Seek { + my ($this, $role) = @_; + + my @roles; + + if (ref($role) eq 'ARRAY') { + @roles = @{$role}; + } elsif (not ref($role)) { + @roles = split(/\s+/, $role); + } else { + die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); + } + + + for(my $r = $this; $r; $r = $r->parent) { + return $r if $r->HasRole(@roles); + } + return; +} + +sub HasRole { + my ($this, @roles) = @_; + my %cache = map { $_, 1 } @{$this->role}; + return scalar(grep not($cache{$_}), @roles) ? 0 : 1; +} + +sub _InvokeDelegate { + my $delegate = shift; + + return $delegate->(@_) if ref $delegate eq 'CODE'; + return $delegate->Invoke(@_) if eval { $delegate->can('Invoke') }; +} + +1; + +__END__ + +=pod + +=head1 NAME + +C - Web-ресурс. + +=head1 SYNOPSIS + +Класс для внутреннего использования. Объединяет в себе контракт и модель данных. +Основная задача - обработать поступающий от контроллера запрос на вызов C +метода. + +Экземпляры данного класса передаются в качестве параметров делегатам +осуществляющим привязку к модели в C +и C. + +=head1 DESCRIPTION + +Весь функционал ресурса, поддерживаемые им C методы определяются +контрактом. Однако можно реализовывать ресурсы, которые не имеют контракта +или он отличается от того, что предоставляется стандартно +C. + +Каждый ресурс является контейнером, тоесть позволяет получить дочерний ресурс +по идентифифкатору, если таковой имеется, тоесть ресурс, у которого нет дочерних +ресурсов на самом деле рассматривается как пустой контейнер. + +С ресурсом непосредственно взаимодействует котроллер запросов +C, вызывая два метода. + +=over + +=item * C + +Данный метод возвращает дочерний ресурс, соответствующий C<$childId>. +Текущая реализация использует метод C контракта текущего +ресурса, после чего создает дочерний ресурс. + +Если дочерний ресурс не найден, вызывается исключение +C. + +=item * C + +Обрабатывает запрос к ресурсу. Для этого используется контракт ресурса, в +нем выбирается соответсвующий C. +Затем найденный контракт для указанной операции используется для обработки +запроса. + +=back + +Если объект реализует два вышеуказанных метода, он является веб-ресурсом, а +детали его реализации, котнракт и прочее уже не важно, поэтому можно реализовать +собственный класс ресурса, например унаследованный от +C. + +=head1 MEMBERS + +=head2 C<[get]request> + +Объект C представляющий запрос к серверу. + +=head2 C<[get]application> + +Ссылка на приложение, к которому относится данный ресурс. Получается +автоматически из объекта запроса. + +=head2 C<[get]contract> + +Обязательное свойство для ресурса, ссылается, на контракт, соответствующий +данному ресурсу, используется для выполнения C методов и получения +дочерних ресурсов. + +=head2 C<[get]id> + +Обязательное свойство ресурса, идентифицирует его в родительском контейнере, +для корневого ресурса может иметь произвольное значение. + +=head2 C<[get]parent> + +Ссылка на родительский ресурс, для корневого ресурса не определена. + +=head2 C<[get]model> + +Ссылка на объект предметной области, представляемый данным ресурсом. Данное +свойство не является обязательным и может быть не задано. + +=head2 C<[get]location> + +Объект типа C или аналогичный описывающий адрес текущего +ресурса, может быть как явно передан при создании ресурса, так и вычислен +автоматически (только для ресурсов имеющих родителя). Следует заметить, что +адрес ресурса не содержит параметров запроса, а только путь. + +=head2 C<[get,list]role> + +Список ролей ресурса. Роль это условный маркер, который позволяет определить +функции выполняемые ресурсом, например контейнер, профиль пользователя и т.п. + +Используется при построении цепочек навигации, а также при поиске с использованием +метода C. + +=head2 C + +Ищет ресурс в цепочке родителей (включая сам ресурс) с подходящими ролями. + +Роли могут быть переданы в виде массива или строки, где роли разделены пробелами + +=head2 C<[get]FetchChildResource($id)> + +Возвращает дочерний ресурс, по его идентификатору. + +Данная реализация использует контракт текущего ресурса для поиска информации о +дочернем ресурсе C<< $this->contract->FindChildResourceInfo($id) >>. + +Затем осуществляется привязка к моделе, тоесть, выполняется делегат, для +получения модели дочернего ресурса, а затем осуществляется привязка к контракту, +при этом в делегат, который должен вернуть контракт дочернего ресурса передаются +текущий ресурc и модель дочернего ресурса. + +=cut diff -r d5c8b955bf8d -r e12c14177848 Lib/IMPL/Web/Handler/View.pm --- a/Lib/IMPL/Web/Handler/View.pm Fri Dec 13 16:49:47 2013 +0400 +++ b/Lib/IMPL/Web/Handler/View.pm Tue Dec 24 20:01:55 2013 +0400 @@ -10,7 +10,7 @@ Factory => 'IMPL::Web::View::ObjectFactory', HttpResponse => 'IMPL::Web::HttpResponse', Loader => 'IMPL::Code::Loader', - ViewResult => '-IMPL::Web::ViewResult', + ViewResult => 'IMPL::Web::ViewResult', Security => 'IMPL::Security' }, base => [ @@ -58,7 +58,8 @@ layout => $this->layout, document => {}, session => sub { Security->context }, - user => sub { Security->principal } + user => sub { Security->principal }, + security => sub { $action->security } }; my %responseParams = (