Mercurial > pub > Impl
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 371:d5c8b955bf8d | 372:e12c14177848 |
|---|---|
| 1 package IMPL::Web::Application::ResourceBase; | |
| 2 use strict; | |
| 3 | |
| 4 use URI; | |
| 5 use Carp qw(carp); | |
| 6 use IMPL::lang qw(:hash :base); | |
| 7 use IMPL::Const qw(:prop); | |
| 8 use IMPL::declare { | |
| 9 require => { | |
| 10 | |
| 11 Exception => 'IMPL::Exception', | |
| 12 ArgumentException => '-IMPL::InvalidArgumentException', | |
| 13 OperationException => '-IMPL::InvalidOperationException', | |
| 14 NotAllowedException => 'IMPL::Web::NotAllowedException', | |
| 15 | |
| 16 }, | |
| 17 base => [ | |
| 18 'IMPL::Object' => undef, | |
| 19 'IMPL::Web::Application::ResourceInterface' => undef | |
| 20 ], | |
| 21 props => [ | |
| 22 request => PROP_RO, | |
| 23 application => PROP_RO, | |
| 24 parent => PROP_RO, | |
| 25 model => PROP_RO, | |
| 26 id => PROP_RO, | |
| 27 location => PROP_RO, | |
| 28 role => PROP_RO | PROP_LIST | |
| 29 ] | |
| 30 }; | |
| 31 | |
| 32 sub CTOR { | |
| 33 my ( $this, %args ) = @_; | |
| 34 | |
| 35 die ArgumentException->new(request => 'A request object must be specified') | |
| 36 unless $args{request}; | |
| 37 | |
| 38 $this->request( $args{request} ); | |
| 39 $this->parent( $args{parent} ); | |
| 40 $this->model( $args{model} ); | |
| 41 $this->id( $args{id} ); | |
| 42 $this->application( $args{request}->application ); | |
| 43 | |
| 44 # если расположение явно не указано, то оно вычисляется автоматически, | |
| 45 # либо остается не заданным | |
| 46 $this->location( $args{location} | |
| 47 || eval { $this->parent->location->Child( $this->id ) } ); | |
| 48 | |
| 49 if (my $role = $args{role}) { | |
| 50 if (ref($role) eq 'ARRAY') { | |
| 51 $this->role($role); | |
| 52 } elsif (not ref($role)) { | |
| 53 $this->role(split(/\s+/, $role)); | |
| 54 } else { | |
| 55 die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); | |
| 56 } | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 sub InvokeHttpVerb { | |
| 61 my ( $this, $verb ) = @_; | |
| 62 | |
| 63 my $operation = $this->verbs->{ lc($verb) }; | |
| 64 | |
| 65 die NotAllowedException->new( | |
| 66 allow => join( ',', $this->GetAllowedMethods ) ) | |
| 67 unless $operation; | |
| 68 | |
| 69 $this->AccessCheck($verb); | |
| 70 my $request = $this->request; | |
| 71 | |
| 72 # в случае, когда один ресурс вызывает HTTP метод другого ресурса, нужно | |
| 73 # сохранить оригинальный resourceLocation | |
| 74 $request->context->{resourceLocation} ||= $this->location; | |
| 75 | |
| 76 # это свойство специфично только для REST приложений. | |
| 77 # сохранение текущего ресурса не повлечет за собой существенных расходов, | |
| 78 # т.к. они просто освободятся несколько позже. | |
| 79 if ( not $request->context->{resource} ) { | |
| 80 $request->context->{resource} = $this; | |
| 81 $request->context->{environment} = sub { | |
| 82 carp "using request environment is deprecated"; | |
| 83 $this->PrepareEnvironment() | |
| 84 }; | |
| 85 } | |
| 86 | |
| 87 return _InvokeDelegate( $operation, $this, $request ); | |
| 88 } | |
| 89 | |
| 90 sub security { | |
| 91 shift->request->security | |
| 92 } | |
| 93 | |
| 94 sub verbs { | |
| 95 {} # возвращаем пстой список операций | |
| 96 } | |
| 97 | |
| 98 sub GetAllowedMethods { | |
| 99 map( uc, keys %{ shift->verbs } ); | |
| 100 } | |
| 101 | |
| 102 sub AccessCheck { | |
| 103 | |
| 104 } | |
| 105 | |
| 106 sub Seek { | |
| 107 my ($this, $role) = @_; | |
| 108 | |
| 109 my @roles; | |
| 110 | |
| 111 if (ref($role) eq 'ARRAY') { | |
| 112 @roles = @{$role}; | |
| 113 } elsif (not ref($role)) { | |
| 114 @roles = split(/\s+/, $role); | |
| 115 } else { | |
| 116 die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); | |
| 117 } | |
| 118 | |
| 119 | |
| 120 for(my $r = $this; $r; $r = $r->parent) { | |
| 121 return $r if $r->HasRole(@roles); | |
| 122 } | |
| 123 return; | |
| 124 } | |
| 125 | |
| 126 sub HasRole { | |
| 127 my ($this, @roles) = @_; | |
| 128 my %cache = map { $_, 1 } @{$this->role}; | |
| 129 return scalar(grep not($cache{$_}), @roles) ? 0 : 1; | |
| 130 } | |
| 131 | |
| 132 sub _InvokeDelegate { | |
| 133 my $delegate = shift; | |
| 134 | |
| 135 return $delegate->(@_) if ref $delegate eq 'CODE'; | |
| 136 return $delegate->Invoke(@_) if eval { $delegate->can('Invoke') }; | |
| 137 } | |
| 138 | |
| 139 1; | |
| 140 | |
| 141 __END__ | |
| 142 | |
| 143 =pod | |
| 144 | |
| 145 =head1 NAME | |
| 146 | |
| 147 C<IMPL::Web::Application::Resource> - Web-ресурс. | |
| 148 | |
| 149 =head1 SYNOPSIS | |
| 150 | |
| 151 Класс для внутреннего использования. Объединяет в себе контракт и модель данных. | |
| 152 Основная задача - обработать поступающий от контроллера запрос на вызов C<HTTP> | |
| 153 метода. | |
| 154 | |
| 155 Экземпляры данного класса передаются в качестве параметров делегатам | |
| 156 осуществляющим привязку к модели в C<IMPL::Web::Application::ResourceContract> | |
| 157 и C<IMPL::Web::Application::OperationContract>. | |
| 158 | |
| 159 =head1 DESCRIPTION | |
| 160 | |
| 161 Весь функционал ресурса, поддерживаемые им C<HTTP> методы определяются | |
| 162 контрактом. Однако можно реализовывать ресурсы, которые не имеют контракта | |
| 163 или он отличается от того, что предоставляется стандартно | |
| 164 C<IMPL::Web::Application::ResourceContract>. | |
| 165 | |
| 166 Каждый ресурс является контейнером, тоесть позволяет получить дочерний ресурс | |
| 167 по идентифифкатору, если таковой имеется, тоесть ресурс, у которого нет дочерних | |
| 168 ресурсов на самом деле рассматривается как пустой контейнер. | |
| 169 | |
| 170 С ресурсом непосредственно взаимодействует котроллер запросов | |
| 171 C<IMPL::Web::Handler::RestController>, вызывая два метода. | |
| 172 | |
| 173 =over | |
| 174 | |
| 175 =item * C<FetchChildResource($childId)> | |
| 176 | |
| 177 Данный метод возвращает дочерний ресурс, соответствующий C<$childId>. | |
| 178 Текущая реализация использует метод C<FindChildResourceInfo> контракта текущего | |
| 179 ресурса, после чего создает дочерний ресурс. | |
| 180 | |
| 181 Если дочерний ресурс не найден, вызывается исключение | |
| 182 C<IMPL::Web::NotFoundException>. | |
| 183 | |
| 184 =item * C<InvokeHttpVerb($verb,$action)> | |
| 185 | |
| 186 Обрабатывает запрос к ресурсу. Для этого используется контракт ресурса, в | |
| 187 нем выбирается соответсвующий C<IMPL::Web::Application::OperationContract>. | |
| 188 Затем найденный контракт для указанной операции используется для обработки | |
| 189 запроса. | |
| 190 | |
| 191 =back | |
| 192 | |
| 193 Если объект реализует два вышеуказанных метода, он является веб-ресурсом, а | |
| 194 детали его реализации, котнракт и прочее уже не важно, поэтому можно реализовать | |
| 195 собственный класс ресурса, например унаследованный от | |
| 196 C<IMPL::Web::Application::CustomResource>. | |
| 197 | |
| 198 =head1 MEMBERS | |
| 199 | |
| 200 =head2 C<[get]request> | |
| 201 | |
| 202 Объект C<IMPL::Web::Application::Action> представляющий запрос к серверу. | |
| 203 | |
| 204 =head2 C<[get]application> | |
| 205 | |
| 206 Ссылка на приложение, к которому относится данный ресурс. Получается | |
| 207 автоматически из объекта запроса. | |
| 208 | |
| 209 =head2 C<[get]contract> | |
| 210 | |
| 211 Обязательное свойство для ресурса, ссылается, на контракт, соответствующий | |
| 212 данному ресурсу, используется для выполнения C<HTTP> методов и получения | |
| 213 дочерних ресурсов. | |
| 214 | |
| 215 =head2 C<[get]id> | |
| 216 | |
| 217 Обязательное свойство ресурса, идентифицирует его в родительском контейнере, | |
| 218 для корневого ресурса может иметь произвольное значение. | |
| 219 | |
| 220 =head2 C<[get]parent> | |
| 221 | |
| 222 Ссылка на родительский ресурс, для корневого ресурса не определена. | |
| 223 | |
| 224 =head2 C<[get]model> | |
| 225 | |
| 226 Ссылка на объект предметной области, представляемый данным ресурсом. Данное | |
| 227 свойство не является обязательным и может быть не задано. | |
| 228 | |
| 229 =head2 C<[get]location> | |
| 230 | |
| 231 Объект типа C<IMPL::Web::AutoLocator> или аналогичный описывающий адрес текущего | |
| 232 ресурса, может быть как явно передан при создании ресурса, так и вычислен | |
| 233 автоматически (только для ресурсов имеющих родителя). Следует заметить, что | |
| 234 адрес ресурса не содержит параметров запроса, а только путь. | |
| 235 | |
| 236 =head2 C<[get,list]role> | |
| 237 | |
| 238 Список ролей ресурса. Роль это условный маркер, который позволяет определить | |
| 239 функции выполняемые ресурсом, например контейнер, профиль пользователя и т.п. | |
| 240 | |
| 241 Используется при построении цепочек навигации, а также при поиске с использованием | |
| 242 метода C<seek>. | |
| 243 | |
| 244 =head2 C<seek($role)> | |
| 245 | |
| 246 Ищет ресурс в цепочке родителей (включая сам ресурс) с подходящими ролями. | |
| 247 | |
| 248 Роли могут быть переданы в виде массива или строки, где роли разделены пробелами | |
| 249 | |
| 250 =head2 C<[get]FetchChildResource($id)> | |
| 251 | |
| 252 Возвращает дочерний ресурс, по его идентификатору. | |
| 253 | |
| 254 Данная реализация использует контракт текущего ресурса для поиска информации о | |
| 255 дочернем ресурсе C<< $this->contract->FindChildResourceInfo($id) >>. | |
| 256 | |
| 257 Затем осуществляется привязка к моделе, тоесть, выполняется делегат, для | |
| 258 получения модели дочернего ресурса, а затем осуществляется привязка к контракту, | |
| 259 при этом в делегат, который должен вернуть контракт дочернего ресурса передаются | |
| 260 текущий ресурc и модель дочернего ресурса. | |
| 261 | |
| 262 =cut |
