Mercurial > pub > Impl
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 |