comparison Lib/IMPL/Web/Application/RestResource.pm @ 202:5146e17a7b76

IMPL::Web::Application::RestResource fixes, documentation
author sergey
date Wed, 25 Apr 2012 02:49:23 +0400
parents 0c018a247c8a
children 292226770180
comparison
equal deleted inserted replaced
201:0c018a247c8a 202:5146e17a7b76
1 package IMPL::Web::Application::RestResource; 1 package IMPL::Web::Application::RestResource;
2 use strict; 2 use strict;
3 3
4 use IMPL::lang qw(:declare :constants is); 4 use IMPL::lang qw(:declare :constants is :hash);
5 use IMPL::Exception(); 5 use IMPL::Exception();
6 6
7 use IMPL::declare { 7 use IMPL::declare {
8 require => { 8 require => {
9 ForbiddenException => 'IMPL::Web::ForbiddenException', 9 ForbiddenException => 'IMPL::Web::ForbiddenException',
10 NotFoundException => 'IMPL::Web::NotFoundException', 10 NotFoundException => 'IMPL::Web::NotFoundException',
11 InvalidOpException => '-IMPL::InvalidOperationException', 11 InvalidOpException => '-IMPL::InvalidOperationException',
12 ArgumentException => '-IMPL::InvalidArgumentException', 12 ArgumentException => '-IMPL::InvalidArgumentException',
13 TTransform => '-IMPL::Transform', 13 TTransform => '-IMPL::Transform',
14 TResolve => '-IMPL::Config::Resolve', 14 TResolve => '-IMPL::Config::Resolve',
15 CustomResource => 'IMPL::Web::Application::CustomResource' 15 CustomResource => 'IMPL::Web::Application::RestCustomResource'
16 }, 16 },
17 base => { 17 base => {
18 'IMPL::Web::Application::RestCustomResource' => '@_' 18 'IMPL::Web::Application::RestCustomResource' => '@_'
19 } 19 }
20 }; 20 };
35 my ($this) = @_; 35 my ($this) = @_;
36 36
37 die ArgumentException->new("target") unless $this->target; 37 die ArgumentException->new("target") unless $this->target;
38 38
39 $this->final($this->childRegex ? 0 : 1); 39 $this->final($this->childRegex ? 0 : 1);
40 $this->methods({}) unless $this->methods;
40 41
41 if ($this->enableForms) { 42 if ($this->enableForms) {
42 $this->methods->{create} = { 43 $this->methods->{create} = {
43 get => \&_ParentGet, 44 get => $this->get,
44 post => \&_ParentPost, 45 post => $this->post,
45 final => 1 # this resource doesn't have any children 46 final => 1 # this resource doesn't have any children
46 }; 47 } if $this->post;
47 48
48 $this->methods->{edit} = { 49 $this->methods->{edit} = {
49 get => \&_ParentGet, 50 get => $this->get,
50 post => \&_ParentPut, 51 post => $this->put,
51 final => 1 # this resource doesn't have any children 52 final => 1 # this resource doesn't have any children
52 }; 53 } if $this->put;
53 54
54 $this->methods->{delete} { 55 $this->methods->{delete} {
55 get => \&_ParentGet, 56 get => $this->get,
56 post => \&_ParentDelete 57 post => $this->delete,
57 final => 1 # this resource doesn't have any children 58 final => 1 # this resource doesn't have any children
58 } 59 } if $this->delete;
59 } 60 }
60 } 61 }
61 62
62 sub _ParentGet { 63 # создает дочерний ресурс из описания, однако все методы созданного
63 my ($this,$action) = @_; 64 # ресурса переадресуются к его родителю, это нужно, чтобы публиковать
64 return $this->parent->GetImpl($action); 65 # методы и свойства объекта
65 } 66 sub _CreateSubResource {
66 67 my ($this,$resource,$id) = @_;
67 sub _ParentPut { 68
68 my ($this,$action) = @_; 69 my %methods = map {
69 return $this->parent->PutImpl($action); 70 my $method = $resource->{$_};
70 } 71 $_ => sub {
71 72 my ($this,$action) = @_;
72 sub _ParentPost { 73 return $this->parent->InvokeMember($method,$action);
73 my ($this,$action) = @_; 74 };
74 return $this->parent->PostImpl($action); 75 } grep $resource->{$_}, qw(get post put delete);
75 } 76
76 77 return CustomResource->new(
77 sub _ParentDelete { 78 %methods,
78 my ($this,$action) = @_; 79 final => $resource->{final},
79 return $this->parent->DeleteImpl($action); 80 parent => $this,
81 id => $id,
82 contract => $this->contract
83 );
80 } 84 }
81 85
82 sub FetchChildResource { 86 sub FetchChildResource {
83 my ($this,$id,$action) = @_; 87 my ($this,$id,$action) = @_;
84 88
91 my $method = $this->index; 95 my $method = $this->index;
92 die ForbiddenException->new() unless $method; 96 die ForbiddenException->new() unless $method;
93 97
94 $res = $this->InvokeMember($method,$action); 98 $res = $this->InvokeMember($method,$action);
95 99
96 } elsif (my $resource = $this->methods->{$id}) { 100 } elsif ($this->methods and my $resource = $this->methods->{$id}) {
97 return CustomResource->new( 101
98 get => $resource->{get}, 102 return $this->_CreateSubResource($resource,$id);
99 post => $resource->{post}, 103
100 put => $resource->{put}, 104 } elsif ($rx and $id =~ m/^$rx$/ and my $method = $this->fetch) {
101 delete => $resource->{delete}, 105
102 parent => $this, 106 $method = {
103 id => $id, 107 method => $method,
104 target => $this->target 108 parameters => 'id'
105 ); 109 } unless ref $method;
106 110
107 } elsif ($rx and $id =~ m/^$rx$/ and $method = $this->fetch) {
108 $res = $this->InvokeMember($method,$action, { id => $id } ); 111 $res = $this->InvokeMember($method,$action, { id => $id } );
109 } else { 112
110 die ForbiddenException->new();
111 } 113 }
112 114
113 die NotFoundException->new() unless defined $res; 115 die NotFoundException->new() unless defined $res;
114 116
115 return $this->contract->Transform($res, {parent => $this, id => $id} ); 117 return $this->contract->Transform($res, {parent => $this, id => $id} );
118 }
119
120 sub InvokeMember {
121 my ($this,$method,$action,$predefined) = @_;
122
123 die ArgumentException->new("method","No method information provided") unless $method;
124
125 #normalize method info
126 if (not ref $method) {
127 $method = {
128 method => $method
129 };
130 }
131
132 if (ref $method eq 'HASH') {
133 my $member = $method->{method} or die InvalidOpException->new("A member name isn't specified");
134
135 $member = $member->Invoke($this) if eval { $member->isa(TResolve) };
136
137 my @args;
138
139 if (my $params = $method->{parameters}) {
140 if (ref $params eq 'HASH') {
141 @args = map {
142 $_,
143 $this->MakeParameter($params->{$_},$action,$predefined)
144 } keys %$params;
145 } elsif (ref $params eq 'ARRAY') {
146 @args = map $this->MakeParameter($_,$action,$predefined), @$params;
147 } else {
148 @args = ($this->MakeParameter($params,$action,$predefined));
149 }
150 }
151 return $this->target->$member(@args);
152 } elsif (ref $method eq TResolve) {
153 return $method->Invoke($this);
154 } elsif (ref $method eq 'CODE') {
155 return $method->($this,$action);
156 } else {
157 die InvalidOpException->new("Unsupported type of the method information", ref $method);
158 }
159 }
160
161 sub MakeParameter {
162 my ($this,$param,$action,$predefined) = @_;
163
164 my $params = hashApply(
165 {
166 id => $this->id,
167 action => $action,
168 query => $action->query
169 },
170 $predefined || {}
171 );
172
173
174
175 if ($param) {
176 if (is $param, TTransform ) {
177 return $param->Transform($action->query);
178 } elsif ($param and not ref $param) {
179 return $params->{$param} || $action->query->param($param);
180 } else {
181 die InvalidOpException->new("Unsupported parameter mapping", $param);
182 }
183 } else {
184 return undef;
185 }
116 } 186 }
117 187
118 1; 188 1;
119 189
120 __END__ 190 __END__
190 method => 'Vote', 260 method => 'Vote',
191 parameters => [qw(id rating comment)] 261 parameters => [qw(id rating comment)]
192 } 262 }
193 } 263 }
194 } 264 }
195 list => 'search', 265 index => {
266 method => 'search',
267 paremeters => [qw(filter page limit)]
268 },
196 fetch => 'GetItemById' 269 fetch => 'GetItemById'
197 } 270 }
198 ); 271 );
199 272
200 =end code 273 =end code
201 274
202 =head1 DESCRIPTION 275 =head1 DESCRIPTION
203 276
204 Каждый ресурс представляет собой коллекцию и реализует методы C<HTTP> C<GET,POST,PUT,DELETE>. 277 Каждый ресурс представляет собой коллекцию и реализует методы C<HTTP> C<GET,POST,PUT,DELETE>.
205 278
206 Ресурсы выстраиваются в иерархию, на основе пути. Поиск конечного реурса происходит последовательным 279 Вызов каждого из этих методов позволяет выполнить одну из операций над ресурсом, однако
207 вызовом метода GET с именем очередного ресурса. 280 операций может быть больше, для этого создаются дочерние ресурсы (каждый из которых также
208 281 может иметь четыре метода C<GET,POST,PUT,DELETE>), однако обращения к методам у дочерних
282 ресурсов отображаются в вызовы методов у родительского ресурса.
283
284 Такой подход позволяет расширить функциональность не изменяя стандарт C<HTTP>, а также обойти
285 ограничения браузеров на методы C<PUT,DELETE>.
286
287 Данный тип ресутсов расчитан на использование с конфигурацией, которую можно будет
288 сохранить или прочитать, например, из файла. Для этого у ресурса есть ряд настроек,
289 которые позволяют в простой форме задать отображения между C<HTTP> методами и методами
290 объекта представленного данным ресурсом.
291
209 292
210 =head2 HTTP METHODS 293 =head2 HTTP METHODS
211 294
212 =head3 C<GET> 295 =head3 C<GET>
213 296
214 Возвращает коллекцию дочерних ресурсов. 297 Возвращает данные из текущего ресурса. Обращение к данному методу не должно вносить
215 298 изменений в ресурсы.
216 =head3 C<GET {id}> 299
217 300 =head3 C<PUT>
218 Возвращает дочерний объект с идентификатором C<id> 301
219 302 Обновляет ресурс. Повторное обращение к данному методу должно приводить к одному и
220 =head3 C<GET {method}> 303 томуже результату.
221 304
222 Вызывает метод C<method> и возвращает его результаты. При публикации методов доступных 305 =head3 C<DELETE>
223 через C<GET> данные методы не должны вносить изменений в предметную область. 306
224 307 Удаляет ресурс.
225 =head3 C<PUT {id}>
226
227 Обновляет дочерний ресурс с указанным идентификатором.
228
229 =head3 C<DELETE {id}>
230
231 Удаляет дочерний ресурс с указанным идентификатором.
232 308
233 =head3 C<POST> 309 =head3 C<POST>
234 310
235 Добавляет новый дочерний ресурс в коллекцию. 311 Данный метод может вести себя как угодно, однако обычно он используется для добавления
236 312 нового дочернего ресурса в коллекцию,также может использоваться для вызова метода, в случае
237 =head3 C<POST {method}> 313 если происходит публикация методов в качестве дочерних ресурсов.
238
239 Вызывает метод C<method>.
240
241 =head2 HTTP METHOD MAPPING
242
243 =head3 C<POST {method}>
244
245 Вызывает метод C<method>, в отличии от C<GET> методы опубликованные через C<POST> могут вносить
246 изменения в объекты.
247 314
248 =head1 BROWSER COMPATIBILITY 315 =head1 BROWSER COMPATIBILITY
249 316
250 Однако существует проблема с браузерами, поскольку тег C<< <form> >> реализет только методы 317 Однако существует проблема с браузерами, поскольку тег C<< <form> >> реализет только методы
251 C<GET,POST>. Для решения данной проблемы используется режим совместимости C<enableForms>. В 318 C<GET,POST>. Для решения данной проблемы используется режим совместимости C<enableForms>. В
252 случае когда данный режим активен, автоматически публикуются дочерние ресурсы C<create,edit,delete>. 319 случае когда данный режим активен, автоматически публикуются дочерние ресурсы C<create,edit,delete>.
253 320
254 =head2 C<GET create> 321 Данные ресуры пбликуются как методы, что означает то, что обращения к ним будут превращены в
255 322 выполнения соответсвующих методов на родительском объекте.
256 Возвращает C<target>. 323
257 324 =head2 C<create>
258 =head2 C<POST create> 325
259 326 По сути данные ресурс не является необходимостью, однако создается для целостности модели.
260 Вызывает метод C<PostImpl> передавая ему свои параметры. 327
261 328 =head3 C<GET>
262 =head2 C<GET edit> 329
263 330 Передает управление методу C<get>
264 Возвращает C<target>. 331
265 332 =head3 C<POST>
266 =head2 C<POST edit> 333
267 334 Передает управление методу C<post>
268 Вызывает метод C<$this->parent->PutImpl($this->id)> передавая ему свои параметры. 335
269 336 =head2 C<edit>
270 =head2 C<GET delete>. 337
271 338 =head3 C<GET>
272 Возвращает C<target>. 339
273 340 Передает управление методу C<get>
274 =head2 C<POST delete>. 341
275 342 =head3 C<POST>
276 Вызывает метод C<$this->parent->DeleteImpl($this->id)> передавая ему свои параметры. 343
344 Передает управление методу C<put>, как если бы он был выполнен непосредственно у
345 родительского ресурса.
346
347 =head2 C<delete>
348
349 =head3 C<GET>
350
351 Передает управление методу C<get>
352
353 =head3 C<POST>
354
355 Передает управление методу C<delete>, , как если бы он был выполнен непосредственно у
356 родительского ресурса.
357
358 =head1 METHOD DEFINITIONS
359
360 Все методы ресурсов данного типа задаются описаниями, хранящимися в соответствующих
361 свойствах. Когда наступает необходимость вызова соответствующего метода, его описание
362 бедется из свойства и передается методу C<InvokeMember>, который и производит вызов.
363
364 =head2 C<HASH>
365
366 Содержит в себе описание метода, который нужно вызвать, а также его параметры.
367
368 =over
369
370 =item C<method>
371
372 Имя метода который будет вызван.
373
374 =item C<paremeters>
375
376 Описание параметров метода, может быть либо массивом, либо хешем, либо простым
377 значением.
378
379 =over
380
381 =item C<ARRAY>
382
383 Метод получает список параметров, каждый элемент данного массива будет превращен
384 в параметр при помощи метода C<MakeParameter>
385
386 =item C<HASH>
387
388 Метод получает список параметров, который состоит пар ключ-значение, каждое значение
389 данного хеша будет превращено в зачение параметра метода при помощи метода C<MakeParameter>.
390 Ключи хеша изменениям не подвергаются.
391
392 =item Простое значение
393
394 Метод получает одно значение, которое будет получено из текущего при помощи C<MakeParameter>.
395
396 =back
397
398 =back
399
400 =head2 C<CODE>
401
402 Если в описании метода находится ссылка на функцию, то эта функция будет вызвана с параметрами.
403 Данный вариант полезен когда ресурсы создаются програмно обычного механизма описаний не достаточно
404 для реализации требуемого функционала.
405
406 =over
407
408 =item C<$resource>
409
410 Текущий ресурс у которого производится вызов метода.
411
412 =item C<$action>
413
414 Текущий запрос C<IMPL::Web::Application::Action>.
415
416 =back
417
418 =head2 Простое значение
419
420 Интерпретируется как имя метода у объекта данных текущего ресурса.
277 421
278 =head1 MEMBERS 422 =head1 MEMBERS
279 423
280 =head2 C<[get]id> 424 =head2 C<[get]id>
281 425
282 Идентификатор текущего ресурса. 426 Идентификатор текущего ресурса.
283 427
284 =head2 C<[get]target> 428 =head2 C<[get]target>
285 429
286 Объект (также может быть и класс), обеспечивающий функционал ресурса. 430 Объект данных (может быть и класс, поскольку у него будут только вызываться
431 методы), обеспечивающий функционал ресурса.
287 432
288 =head2 C<[get]parent> 433 =head2 C<[get]parent>
289 434
290 Родительский ресурс, в котором находится текущий ресурс. Может быть C<undef>, 435 Родительский ресурс, в котором находится текущий ресурс. Может быть C<undef>,
291 если текущий ресурс является корнем. 436 если текущий ресурс является корнем.
303 448
304 Содержит описание метода для получения дочернего объекта. Если данный метод 449 Содержит описание метода для получения дочернего объекта. Если данный метод
305 отсутствует, то дочерние ресурсы не получится адресовать относительно данного. 450 отсутствует, то дочерние ресурсы не получится адресовать относительно данного.
306 По умолчанию получает идентификатор дочернего ресурса первым параметром. 451 По умолчанию получает идентификатор дочернего ресурса первым параметром.
307 452
308 =head2 C<[get]list> 453 =head2 C<[get]index>
309 454
310 Описание метода для получения списка дочерних объектов. По умолчанию не 455 Описание метода для получения списка дочерних объектов. По умолчанию не
311 получает параметров. 456 получает параметров.
312 457
313 =head2 C<[get]insert> 458 =head2 C<[get]post>
314 459
315 Описание метода для добавление дочернего ресурса. По умолчанию получает 460 Описание метода для добавление дочернего ресурса. По умолчанию получает
316 объект C<CGI> описывабщий текущий запрос первым параметром. 461 объект C<CGI> описывабщий текущий запрос первым параметром.
317 462
318 =head2 C<[get]update> 463 =head2 C<[get]put>
319 464
320 Описание метода для обновления дочернего ресурса. По умолчанию получает 465 Описание метода для обновления дочернего ресурса. По умолчанию получает
321 идентификатор дочернего ресурса и объект C<CGI> текущего запроса. 466 объект C<CGI> текущего запроса.
322 467
323 =head2 C<[get]delete> 468 =head2 C<[get]delete>
324 469
325 Описание метода для удаления дочернего ресурса. По умолчанию получает 470 Описание метода для удаления дочернего ресурса. По умолчанию не получает
326 идентификатор дочернего ресурса. 471 параметров.
327 472
328 =head2 C<GetImpl($child,$action)> 473 =head2 C<GetImpl($action)>
329 474
330 =over 475 =over
331
332 =item C<$child>
333
334 Идентификатор дочернего ресутсра
335 476
336 =item C<$action> 477 =item C<$action>
337 478
338 Текущий запрос C<IMPL::Web::Application::Action>. 479 Текущий запрос C<IMPL::Web::Application::Action>.
339 480
340 =back 481 =back
341 482
342 Переадресует запрос нужному методу внутреннего объекта C<target> при 483 Переадресует запрос нужному методу внутреннего объекта C<target> при
343 помощи C<InvokeMember>. 484 помощи C<InvokeMember>.
344 485
345 =head2 C<PutImpl($child,$action)> 486 =head2 C<PutImpl($action)>
346 487
347 =over 488 =over
348
349 =item C<$child>
350
351 Идентификатор дочернего ресутсра
352 489
353 =item C<$action> 490 =item C<$action>
354 491
355 Текущий запрос C<IMPL::Web::Application::Action>. 492 Текущий запрос C<IMPL::Web::Application::Action>.
356 493
357 =back 494 =back
358 495
359 Переадресует запрос нужному методу внутреннего объекта C<target> при 496 Переадресует запрос нужному методу внутреннего объекта C<target> при
360 помощи C<InvokeMember>. 497 помощи C<InvokeMember>.
361 498
362 =head2 C<PostImpl($child,$action)> 499 =head2 C<PostImpl($action)>
363 500
364 =over 501 =over
365
366 =item C<$child>
367
368 Идентификатор дочернего ресутсра
369 502
370 =item C<$action> 503 =item C<$action>
371 504
372 Текущий запрос C<IMPL::Web::Application::Action>. 505 Текущий запрос C<IMPL::Web::Application::Action>.
373 506
374 =back 507 =back
375 508
376 Переадресует запрос нужному методу внутреннего объекта C<target> при 509 Переадресует запрос нужному методу внутреннего объекта C<target> при
377 помощи C<InvokeMember>. 510 помощи C<InvokeMember>.
378 511
379 =head2 C<DeleteImpl($child,$action)> 512 =head2 C<DeleteImpl($action)>
380 513
381 =over 514 =over
382
383 =item C<$child>
384
385 Идентификатор дочернего ресутсра
386 515
387 =item C<$action> 516 =item C<$action>
388 517
389 Текущий запрос C<IMPL::Web::Application::Action>. 518 Текущий запрос C<IMPL::Web::Application::Action>.
390 519
391 =back 520 =back
392 521
393 Переадресует запрос нужному методу внутреннего объекта C<target> при 522 Переадресует запрос нужному методу внутреннего объекта C<target> при
394 помощи C<InvokeMember>. 523 помощи C<InvokeMember>.
395 524
396 =head2 C<InvokeMember($memberInfo,$child,$action)> 525 =head2 C<InvokeMember($memberInfo,$action,$params)>
397 526
398 =over 527 =over
399 528
400 =item C<$memberInfo> 529 =item C<$memberInfo>
401 530
402 Описание члена внутреннего объекта C<target>, который нужно вызвать. 531 Описание члена внутреннего объекта C<target>, который нужно вызвать.
403 532
404 =item C<$child>
405
406 Идентификатор дочернего ресутсра
407
408 =item C<$action> 533 =item C<$action>
409 534
410 Текущий запрос C<IMPL::Web::Application::Action>. 535 Текущий запрос C<IMPL::Web::Application::Action>.
536
537 =item C<$params>
538
539 Ссылка на хеш с предопределенными параметрами.
411 540
412 =back 541 =back
413 542
414 Вызывает метод внутреннего объекта C<target>, предварительно подготовив 543 Вызывает метод внутреннего объекта C<target>, предварительно подготовив
415 параметры на основе описания C<$memberInfo> и при помощи С<MakeParameter()>. 544 параметры на основе описания C<$memberInfo> и при помощи С<MakeParameter()>.
416 545
417 =head2 C<MakeParameter($paramDef,$child,$action)> 546 =head2 C<MakeParameter($paramDef,$action)>
418 547
419 =over 548 =over
420 549
421 =item C<$paramDef> 550 =item C<$paramDef>
422 551
426 555
427 =over 556 =over
428 557
429 =item C<id> 558 =item C<id>
430 559
431 Идентификатор дочернего ресурса 560 Идентификатор ресурса
432 561
433 =item C<query> 562 =item C<query>
434 563
435 Объект C<CGI> текущего запроса 564 Объект C<CGI> текущего запроса
436 565
445 =back 574 =back
446 575
447 Если описание параметра - объект C<IMPL::Transform>, то будет выполнено это преобразование над C<CGI> 576 Если описание параметра - объект C<IMPL::Transform>, то будет выполнено это преобразование над C<CGI>
448 объектом текущего запроса C<< $paramDef->Transform($action->query) >>. 577 объектом текущего запроса C<< $paramDef->Transform($action->query) >>.
449 578
579 =item C<$action>
580
581 Текущий запрос
582
450 =back 583 =back
451 584
452 =cut 585 =cut