Mercurial > pub > Impl
diff Lib/IMPL/Web/Application/ResourceBase.pm @ 372:e12c14177848
refactoring web resources model, implementing new simplified model
author | cin |
---|---|
date | Tue, 24 Dec 2013 20:01:55 +0400 |
parents | Lib/IMPL/Web/Application/Resource.pm@833e663796c4 |
children | 2287c72f303a |
line wrap: on
line diff
--- /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<IMPL::Web::Application::Resource> - Web-ресурс. + +=head1 SYNOPSIS + +Класс для внутреннего использования. Объединяет в себе контракт и модель данных. +Основная задача - обработать поступающий от контроллера запрос на вызов C<HTTP> +метода. + +Экземпляры данного класса передаются в качестве параметров делегатам +осуществляющим привязку к модели в C<IMPL::Web::Application::ResourceContract> +и C<IMPL::Web::Application::OperationContract>. + +=head1 DESCRIPTION + +Весь функционал ресурса, поддерживаемые им C<HTTP> методы определяются +контрактом. Однако можно реализовывать ресурсы, которые не имеют контракта +или он отличается от того, что предоставляется стандартно +C<IMPL::Web::Application::ResourceContract>. + +Каждый ресурс является контейнером, тоесть позволяет получить дочерний ресурс +по идентифифкатору, если таковой имеется, тоесть ресурс, у которого нет дочерних +ресурсов на самом деле рассматривается как пустой контейнер. + +С ресурсом непосредственно взаимодействует котроллер запросов +C<IMPL::Web::Handler::RestController>, вызывая два метода. + +=over + +=item * C<FetchChildResource($childId)> + +Данный метод возвращает дочерний ресурс, соответствующий C<$childId>. +Текущая реализация использует метод C<FindChildResourceInfo> контракта текущего +ресурса, после чего создает дочерний ресурс. + +Если дочерний ресурс не найден, вызывается исключение +C<IMPL::Web::NotFoundException>. + +=item * C<InvokeHttpVerb($verb,$action)> + +Обрабатывает запрос к ресурсу. Для этого используется контракт ресурса, в +нем выбирается соответсвующий C<IMPL::Web::Application::OperationContract>. +Затем найденный контракт для указанной операции используется для обработки +запроса. + +=back + +Если объект реализует два вышеуказанных метода, он является веб-ресурсом, а +детали его реализации, котнракт и прочее уже не важно, поэтому можно реализовать +собственный класс ресурса, например унаследованный от +C<IMPL::Web::Application::CustomResource>. + +=head1 MEMBERS + +=head2 C<[get]request> + +Объект C<IMPL::Web::Application::Action> представляющий запрос к серверу. + +=head2 C<[get]application> + +Ссылка на приложение, к которому относится данный ресурс. Получается +автоматически из объекта запроса. + +=head2 C<[get]contract> + +Обязательное свойство для ресурса, ссылается, на контракт, соответствующий +данному ресурсу, используется для выполнения C<HTTP> методов и получения +дочерних ресурсов. + +=head2 C<[get]id> + +Обязательное свойство ресурса, идентифицирует его в родительском контейнере, +для корневого ресурса может иметь произвольное значение. + +=head2 C<[get]parent> + +Ссылка на родительский ресурс, для корневого ресурса не определена. + +=head2 C<[get]model> + +Ссылка на объект предметной области, представляемый данным ресурсом. Данное +свойство не является обязательным и может быть не задано. + +=head2 C<[get]location> + +Объект типа C<IMPL::Web::AutoLocator> или аналогичный описывающий адрес текущего +ресурса, может быть как явно передан при создании ресурса, так и вычислен +автоматически (только для ресурсов имеющих родителя). Следует заметить, что +адрес ресурса не содержит параметров запроса, а только путь. + +=head2 C<[get,list]role> + +Список ролей ресурса. Роль это условный маркер, который позволяет определить +функции выполняемые ресурсом, например контейнер, профиль пользователя и т.п. + +Используется при построении цепочек навигации, а также при поиске с использованием +метода C<seek>. + +=head2 C<seek($role)> + +Ищет ресурс в цепочке родителей (включая сам ресурс) с подходящими ролями. + +Роли могут быть переданы в виде массива или строки, где роли разделены пробелами + +=head2 C<[get]FetchChildResource($id)> + +Возвращает дочерний ресурс, по его идентификатору. + +Данная реализация использует контракт текущего ресурса для поиска информации о +дочернем ресурсе C<< $this->contract->FindChildResourceInfo($id) >>. + +Затем осуществляется привязка к моделе, тоесть, выполняется делегат, для +получения модели дочернего ресурса, а затем осуществляется привязка к контракту, +при этом в делегат, который должен вернуть контракт дочернего ресурса передаются +текущий ресурc и модель дочернего ресурса. + +=cut