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