Mercurial > pub > Impl
comparison Lib/IMPL/Web/Application/Resource.pm @ 373:3ca44e23fd1f
implemented new web resource
author | cin |
---|---|
date | Wed, 25 Dec 2013 17:29:38 +0400 |
parents | |
children | 6608db5dcb81 |
comparison
equal
deleted
inserted
replaced
372:e12c14177848 | 373:3ca44e23fd1f |
---|---|
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 Loader => 'IMPL::Code::Loader' | |
18 }, | |
19 base => [ | |
20 'IMPL::Object' => undef, | |
21 'IMPL::Web::Application::ResourceBase' => '@_' | |
22 ], | |
23 props => [ | |
24 access => PROP_RW, | |
25 verbs => PROP_RW, | |
26 children => PROP_RW | |
27 ] | |
28 }; | |
29 | |
30 __PACKAGE__->static_accessor(verbNames => [qw(get post put delete options head)]); | |
31 __PACKAGE__->static_accessor(httpMethodPrefix => 'Http'); | |
32 | |
33 sub CTOR { | |
34 my ($this, %args) = @_; | |
35 | |
36 my %verbs; | |
37 my $httpPrefix = $this->httpMethodPrefix; | |
38 | |
39 foreach my $verb (@{$this->verbNames}) { | |
40 my $method = exists $args{$verb} ? $args{$verb} : $this->can($httpPrefix . ucfirst($verb)); | |
41 $verbs{$verb} = $method | |
42 if $method; | |
43 } | |
44 | |
45 hashApply(\%verbs,$args{verbs}) | |
46 if ref($args{verbs}) eq 'HASH' ; | |
47 | |
48 $this->children($args{children} || $this->GetChildResources()); | |
49 | |
50 $this->access($args{access}) | |
51 if $args{access}; | |
52 | |
53 $this->verbs(\%verbs); | |
54 } | |
55 | |
56 sub _isInvokable { | |
57 my ($this,$method) = @_; | |
58 | |
59 return | |
60 (blessed($method) and $method->can('Invoke')) || | |
61 ref($method) eq 'CODE' | |
62 } | |
63 | |
64 sub _invoke { | |
65 my ($this,$method,@args) = @_; | |
66 | |
67 if(blessed($method) and $method->can('Invoke')) { | |
68 return $method->Invoke($this,@args); | |
69 } elsif(ref($method) eq 'CODE' || (not(ref($method)) and $this->can($method))) { | |
70 return $this->$method(@args); | |
71 } else { | |
72 die OpException->new("Can't invoke the specified method: $method"); | |
73 } | |
74 } | |
75 | |
76 sub HttpGet { | |
77 shift->model; | |
78 } | |
79 | |
80 sub Fetch { | |
81 my ($this,$childId) = @_; | |
82 | |
83 my $children = $this->children | |
84 or die NotFoundException->new( $this->location->url, $childId ); | |
85 | |
86 if (ref($children) eq 'HASH') { | |
87 return $children->{$childId}; | |
88 } else { | |
89 return $this->_invoke($children,$childId); | |
90 } | |
91 } | |
92 | |
93 sub FetchChildResource { | |
94 my ($this,$childId) = @_; | |
95 | |
96 my $info = $this->Fetch($childId); | |
97 | |
98 return $info | |
99 if (is($info,ResourceInterface)); | |
100 | |
101 return $this->CreateChildResource($info, $childId) | |
102 if ref($info) eq 'HASH'; | |
103 | |
104 die OpException->new("Invalid resource description", $childId, $info); | |
105 } | |
106 | |
107 sub CreateChildResource { | |
108 my ($this,$info, $childId) = @_; | |
109 | |
110 my $params = hashApply( | |
111 { | |
112 parent => $this, | |
113 id => $childId, | |
114 request => $this->request, | |
115 class => ResourceClass | |
116 }, | |
117 $info | |
118 ); | |
119 | |
120 $params->{model} = $this->_invoke($params->{model}) | |
121 if $this->_isInvokable($params->{model}); | |
122 | |
123 my $factory = Loader->default->Require($params->{class}); | |
124 | |
125 return $factory->new(%$params); | |
126 } | |
127 | |
128 sub GetChildResources { | |
129 return {}; | |
130 } | |
131 | |
132 1; | |
133 | |
134 __END__ | |
135 | |
136 =pod | |
137 | |
138 =head1 NAME | |
139 | |
140 C<IMPL::Web::Application::Resource> - Ресурс C<REST> веб приложения | |
141 | |
142 =head1 SYNOPSIS | |
143 | |
144 =begin code | |
145 | |
146 use IMPL::require { | |
147 Resource => 'IMPL::Web::Application::Resource', | |
148 Security => 'IMPL::Security', | |
149 NotFoundException => 'IMPL::Web::NotFoundException', | |
150 ForbiddenException => 'IMPL::Web::ForbiddenException' | |
151 }; | |
152 | |
153 my $model = Resource->new( | |
154 get => sub { }, | |
155 verbs => { | |
156 # non-standart verbs placed here | |
157 myverb => sub { } | |
158 }, | |
159 #child resources can be a hash | |
160 chidren => { | |
161 user => { | |
162 # a resource class may be specified optionally | |
163 # class => Resource, | |
164 model => sub { | |
165 return Security->principal | |
166 }, | |
167 # the default get implementation is implied | |
168 # get => sub { shift->model }, | |
169 access => sub { | |
170 my ($this,$verb) = @_; | |
171 die ForbiddenException->new() | |
172 if Security->principal->isNobody | |
173 } | |
174 }, | |
175 catalog => { | |
176 get => sub { | |
177 my $ctx = shift->application->ConnectDb()->AutoPtr(); | |
178 | |
179 return $ctx->products->find_rs({ in_stock => 1 }); | |
180 }, | |
181 # chid resource may be created dynamically | |
182 children => sub { | |
183 # binds model against the parent reource and id | |
184 my ($this,$id) = @_; | |
185 | |
186 ($id) = ($id =~ /^(\w+)$/) | |
187 or die NotFoundException->new($id); | |
188 | |
189 my $ctx = shift->application->ConnectDb()->AutoPtr(); | |
190 | |
191 my $item = $ctx->products->fetch($id); | |
192 | |
193 die NotFoundException->new() | |
194 unless $item; | |
195 | |
196 # return parameters for the new resource | |
197 return { | |
198 model => $item, | |
199 get => sub { shift->model } | |
200 }; | |
201 } | |
202 }, | |
203 # dynamically binds whole child resource. The result of binding is | |
204 # the new resource or a hash with arguments to create one | |
205 posts => sub { | |
206 my ($this,$id) = @_; | |
207 | |
208 # this approach can be used to create a dynamic resource relaying | |
209 # on the type of the model | |
210 | |
211 return Resource->new( | |
212 id => $id, | |
213 parent => $this, | |
214 get => sub { shift->model } | |
215 ); | |
216 | |
217 # ditto | |
218 # parent and id will be mixed in automagically | |
219 # return { get => sub { shift->model} } | |
220 }, | |
221 post_only => { | |
222 get => undef, # remove GET verb implicitly | |
223 post => sub { | |
224 my ($this) = @_; | |
225 } | |
226 } | |
227 } | |
228 ); | |
229 | |
230 =end code | |
231 | |
232 Альтернативный вариант для создания класса ресурса. | |
233 | |
234 =begin code | |
235 | |
236 package MyResource; | |
237 | |
238 use IMPL::declare { | |
239 require => { | |
240 ForbiddenException => 'IMPL::Web::ForbiddenException' | |
241 }, | |
242 base => [ | |
243 'IMPL::Web::Application::Resource' => '@_' | |
244 ] | |
245 }; | |
246 | |
247 sub ds { | |
248 my ($this) = @_; | |
249 | |
250 $this->context->{ds} ||= $this->application->ConnectDb(); | |
251 } | |
252 | |
253 sub InvokeHttpVerb { | |
254 my $this = shift; | |
255 | |
256 $this->ds->Begin(); | |
257 | |
258 my $result = $this->next::method(@_); | |
259 | |
260 # in case of error the data context will be disposed and the transaction | |
261 # will be reverted | |
262 $this->ds->Commit(); | |
263 | |
264 return $result; | |
265 } | |
266 | |
267 # this method is inherited by default | |
268 # sub HttpGet { | |
269 # shift->model | |
270 # | |
271 # } | |
272 | |
273 sub HttpPost { | |
274 my ($this) = @_; | |
275 | |
276 my %data = map { | |
277 $_, | |
278 $this->request->param($_) | |
279 } qw(name description value); | |
280 | |
281 die ForbiddenException->new("The item with the scpecified name can't be created'") | |
282 if(not $data{name} or $this->ds->items->find({ name => $data{name})) | |
283 | |
284 $this->ds->items->insert(\%data); | |
285 | |
286 return $this->NoContent(); | |
287 } | |
288 | |
289 sub Fetch { | |
290 my ($this,$childId) = @_; | |
291 | |
292 my $item = $this->ds->items->find({name => $childId}) | |
293 or die NotFoundException->new(); | |
294 | |
295 # return parameters for the child resource | |
296 return { model => $item, role => "item food" }; | |
297 } | |
298 | |
299 =end code | |
300 | |
301 =cut | |
302 |