comparison lib/IMPL/Web/Application/Resource.pm @ 407:c6e90e02dd17 ref20150831

renamed Lib->lib
author cin
date Fri, 04 Sep 2015 19:40:23 +0300
parents
children
comparison
equal deleted inserted replaced
406:f23fcb19d3c1 407:c6e90e02dd17
1 package IMPL::Web::Application::Resource;
2 use strict;
3
4 use constant {
5 ResourceClass => __PACKAGE__
6 };
7 use Scalar::Util qw(blessed);
8
9 use IMPL::lang qw(:hash :base);
10 use IMPL::Const qw(:prop);
11 use IMPL::declare {
12 require => {
13 Exception => 'IMPL::Exception',
14 OpException => '-IMPL::InvalidOperationException',
15 NotFoundException => 'IMPL::Web::NotFoundException',
16 ResourceInterface => '-IMPL::Web::Application',
17 HttpResponse => 'IMPL::Web::HttpResponse',
18 HttpResponseResource => 'IMPL::Web::Application::HttpResponseResource',
19 Loader => 'IMPL::Code::Loader'
20 },
21 base => [
22 'IMPL::Web::Application::ResourceBase' => '@_'
23 ],
24 props => [
25 access => PROP_RW,
26 verbs => PROP_RW,
27 children => PROP_RW
28 ]
29 };
30
31 __PACKAGE__->static_accessor(verbNames => [qw(get post put delete options head)]);
32 __PACKAGE__->static_accessor(httpMethodPrefix => 'Http');
33
34 sub CTOR {
35 my ($this, %args) = @_;
36
37 my %verbs;
38 my $httpPrefix = $this->httpMethodPrefix;
39
40 foreach my $verb (@{$this->verbNames}) {
41 my $method = exists $args{$verb} ? $args{$verb} : $this->can($httpPrefix . ucfirst($verb));
42 $verbs{$verb} = $method
43 if $method;
44 }
45
46 hashApply(\%verbs,$args{verbs})
47 if ref($args{verbs}) eq 'HASH' ;
48
49 $this->children($args{children} || $this->GetChildResources());
50
51 $this->access($args{access})
52 if $args{access};
53
54 $this->verbs(\%verbs);
55 }
56
57 sub _isInvokable {
58 my ($this,$method) = @_;
59
60 return
61 (blessed($method) and $method->can('Invoke')) ||
62 ref($method) eq 'CODE'
63 }
64
65 sub _invoke {
66 my ($this,$method,@args) = @_;
67
68 if(blessed($method) and $method->can('Invoke')) {
69 return $method->Invoke($this,@args);
70 } elsif(ref($method) eq 'CODE' || (not(ref($method)) and $this->can($method))) {
71 return $this->$method(@args);
72 } else {
73 die OpException->new("Can't invoke the specified method: $method");
74 }
75 }
76
77 sub HttpGet {
78 shift->model;
79 }
80
81 sub AccessCheck {
82 my ($this,$verb) = @_;
83
84 $this->_invoke($this->access,$verb)
85 if $this->access;
86 }
87
88 sub Fetch {
89 my ($this,$childId) = @_;
90
91 my $children = $this->children
92 or die NotFoundException->new( $this->location->url, $childId );
93
94 if (ref($children) eq 'HASH') {
95 if(my $child = $children->{$childId}) {
96 return $this->_isInvokable($child) ? $this->_invoke($child, $childId) : $child;
97 } else {
98 die NotFoundException->new( $this->location->url, $childId );
99 }
100 } elsif($this->_isInvokable($children)) {
101 return $this->_invoke($children,$childId);
102 } else {
103 die OpException->new("Invalid resource description", $childId, $children);
104 }
105 }
106
107 sub FetchChildResource {
108 my ($this,$childId) = @_;
109
110 my $info = $this->Fetch($childId);
111
112 return $info
113 if (is($info,ResourceInterface));
114
115 $info = {
116 response => $info,
117 class => HttpResponseResource
118 }
119 if is($info,HttpResponse);
120
121 return $this->CreateChildResource($info, $childId)
122 if ref($info) eq 'HASH';
123
124 die OpException->new("Invalid resource description", $childId, $info);
125 }
126
127 sub CreateChildResource {
128 my ($this,$info, $childId) = @_;
129
130 my $params = hashApply(
131 {
132 parent => $this,
133 id => $childId,
134 request => $this->request,
135 class => ResourceClass
136 },
137 $info
138 );
139
140 $params->{model} = $this->_invoke($params->{model})
141 if $this->_isInvokable($params->{model});
142
143 my $factory = Loader->default->Require($params->{class});
144
145 return $factory->new(%$params);
146 }
147
148 sub GetChildResources {
149 return {};
150 }
151
152 1;
153
154 __END__
155
156 =pod
157
158 =head1 NAME
159
160 C<IMPL::Web::Application::Resource> - Ресурс C<REST> веб приложения
161
162 =head1 SYNOPSIS
163
164 =begin code
165
166 use IMPL::require {
167 Resource => 'IMPL::Web::Application::Resource',
168 Security => 'IMPL::Security',
169 NotFoundException => 'IMPL::Web::NotFoundException',
170 ForbiddenException => 'IMPL::Web::ForbiddenException'
171 };
172
173 my $model = Resource->new(
174 get => sub { },
175 verbs => {
176 # non-standart verbs placed here
177 myverb => sub { }
178 },
179 #child resources can be a hash
180 children => {
181 user => {
182 # a resource class may be specified optionally
183 # class => Resource,
184 model => sub {
185 return Security->principal
186 },
187 # the default get implementation is implied
188 # get => sub { shift->model },
189 access => sub {
190 my ($this,$verb) = @_;
191 die ForbiddenException->new()
192 if Security->principal->isNobody
193 }
194 },
195 catalog => {
196 get => sub {
197 my $ctx = shift->application->ConnectDb()->AutoPtr();
198
199 return $ctx->products->find_rs({ in_stock => 1 });
200 },
201 # chid resource may be created dynamically
202 children => sub {
203 # binds model against the parent reource and id
204 my ($this,$id) = @_;
205
206 ($id) = ($id =~ /^(\w+)$/)
207 or die NotFoundException->new($id);
208
209 my $ctx = shift->application->ConnectDb()->AutoPtr();
210
211 my $item = $ctx->products->fetch($id);
212
213 die NotFoundException->new()
214 unless $item;
215
216 # return parameters for the new resource
217 return {
218 model => $item,
219 get => sub { shift->model }
220 };
221 }
222 },
223 # dynamically binds whole child resource. The result of binding is
224 # the new resource or a hash with arguments to create one
225 posts => sub {
226 my ($this,$id) = @_;
227
228 # this approach can be used to create a dynamic resource relaying
229 # on the type of the model
230
231 return Resource->new(
232 id => $id,
233 parent => $this,
234 get => sub { shift->model }
235 );
236
237 # ditto
238 # parent and id will be mixed in automagically
239 # return { get => sub { shift->model} }
240 },
241 post_only => {
242 get => undef, # remove GET verb implicitly
243 post => sub {
244 my ($this) = @_;
245 }
246 }
247 }
248 );
249
250 =end code
251
252 Альтернативный вариант для создания класса ресурса.
253
254 =begin code
255
256 package MyResource;
257
258 use IMPL::declare {
259 require => {
260 ForbiddenException => 'IMPL::Web::ForbiddenException'
261 },
262 base => [
263 'IMPL::Web::Application::Resource' => '@_'
264 ]
265 };
266
267 sub ds {
268 my ($this) = @_;
269
270 $this->context->{ds} ||= $this->application->ConnectDb();
271 }
272
273 sub InvokeHttpVerb {
274 my $this = shift;
275
276 $this->ds->Begin();
277
278 my $result = $this->next::method(@_);
279
280 # in case of error the data context will be disposed and the transaction
281 # will be reverted
282 $this->ds->Commit();
283
284 return $result;
285 }
286
287 # this method is inherited by default
288 # sub HttpGet {
289 # shift->model
290 #
291 # }
292
293 sub HttpPost {
294 my ($this) = @_;
295
296 my %data = map {
297 $_,
298 $this->request->param($_)
299 } qw(name description value);
300
301 die ForbiddenException->new("The item with the scpecified name can't be created'")
302 if(not $data{name} or $this->ds->items->find({ name => $data{name}))
303
304 $this->ds->items->insert(\%data);
305
306 return $this->NoContent();
307 }
308
309 sub Fetch {
310 my ($this,$childId) = @_;
311
312 my $item = $this->ds->items->find({name => $childId})
313 or die NotFoundException->new();
314
315 # return parameters for the child resource
316 return { model => $item, role => "item food" };
317 }
318
319 =end code
320
321 =head1 MEMBERS
322
323 =head2 C<[get,set]verbs>
324
325 Хеш с C<HTTP> методами. При попытке вызова C<HTTP> метода, которого нет в этом
326 хеше приводит к исключению C<IMPL::Web::NotAllowedException>.
327
328 =head2 C<[get,set]access>
329
330 Метод для проверки прав доступа. Если не задан, то доспуп возможен для всех.
331
332 =head2 C<[get,set]children>
333
334 Дочерние ресурсы. Дочерние ресурсы могут быть описаны либо в виде хеша, либо
335 в виде метода.
336
337 =head3 C<HASH>
338
339 Данный хещ содержит в себе таблицу идентификаторов дочерних ресурсов и их
340 описаний.
341
342 Описание каждого ресурса представляет собой либо функцию, либо параметры для
343 создания ресурса C<CraeteChildResource>. Если описание в виде функции, то она
344 должна возвращать либо объект типа ресурс либо параметры для его создания.
345
346 =head3 C<CODE>
347
348 Если дочерние ресурсы описаны в виде функции (возможно использовать имя метода
349 класса текущего ресурса), то для получения дочернего ресурса будет вызвана
350 функция с параметрами C<($this,$childId)>, где C<$this> - текущий ресурс,
351 C<$childId> - идентификатор дочернего ресурса, который нужно вернуть.
352
353 Данная функция должна возвратить либо объект типа ресурс, либо ссылку на хеш с
354 параметрами для создания оного при помощи метода
355 C<CreateChildResource($params,$childId)>.
356
357 =head2 C<[virtual]Fetch($childId)>
358
359 Метод для получения дочернего ресурса.
360
361 Возвращает параметры для создания дочернего ресурса, либо уже созданный ресурс.
362 Создание дочернего ресурса происходит при помощи метода C<CreateChildResource()>
363 который добавляет недостающие параметры к возвращенным в данным методом и
364 создает новый ресурс
365
366 =head2 C<CreateChildResource($params,$id)>
367
368 Создает новый дочерний ресурс с указанным идентификатором и параметрами.
369 Автоматически заполняет параметры
370
371 =over
372
373 =item * C<parent>
374
375 =item * C<id>
376
377 =item * C<request>
378
379 =back
380
381 Тип создаваемого ресурса C<IMPL::Web::Application::Resource>, либо указывается
382 в параметре C<class>.
383
384 =head2 C<[virtual]HttpGet()>
385
386 Реализует C<HTTP> метод C<GET>. По-умолчанию возвращает модель.
387
388 Данный метод нужен для того, чтобы ресурс по-умолчанию поддерживал метод C<GET>,
389 что является самым частым случаем, если нужно изменить данное поведение, нужно:
390
391 =over
392
393 =item * Передать в параметр конструктора C<get> значение undef
394
395 =item * Переопределить метод C<HttpGet>
396
397 =item * При проверке прав доступа выдать исключение
398
399 =back
400
401 =cut
402