comparison Lib/IMPL/Web/Application/ResourceContract.pm @ 229:47f77e6409f7

heavily reworked the resource model of the web application: *some ResourcesContraact functionality moved to Resource +Added CustomResource *Corrected action handlers
author sergey
date Sat, 29 Sep 2012 02:34:47 +0400
parents 431db7034a88
children 6d8092d8ce1b
comparison
equal deleted inserted replaced
228:431db7034a88 229:47f77e6409f7
1 package IMPL::Web::Application::ResourceContract; 1 package IMPL::Web::Application::ResourceContract;
2 use strict; 2 use strict;
3 use IMPL::lang qw(:declare); 3 use IMPL::lang qw(:constants);
4 use IMPL::declare { 4 use IMPL::declare {
5 require => { 5 require => {
6 'Exception' => 'IMPL::Exception', 6 'Exception' => 'IMPL::Exception',
7 'ArgumentException' => '-IMPL::ArgumentException', 7 'ArgumentException' => '-IMPL::ArgumentException',
8 'KeyNotFoundException' => '-IMPL::KeyNotFoundException', 8 'KeyNotFoundException' => '-IMPL::KeyNotFoundException',
9 'ResourceClass' => 'IMPL::Web::Application::Resource' 9 'ResourceClass' => 'IMPL::Web::Application::Resource'
10 }, 10 },
11 base => [ 11 base => [ 'IMPL::Object' => undef ],
12 'IMPL::Object' => undef 12 props => [
13 ] 13 resourceFactory => PROP_ALL,
14 verbs => PROP_ALL,
15 _namedResources => PROP_ALL,
16 _regexpResources => PROP_ALL | PROP_LIST,
17 ]
14 }; 18 };
15
16 BEGIN {
17 public property resourceFactory => PROP_ALL;
18 public property operations => PROP_ALL;
19 private property _namedResources => PROP_ALL;
20 private property _regexpResources => PROP_ALL | PROP_LIST;
21 }
22 19
23 sub CTOR { 20 sub CTOR {
24 my $this = shift; 21 my $this = shift;
25 my %args = @_; 22 my %args = @_;
26 23
27 $this->resourceFactory( $args{resourceFactory} || ResourceClass ); 24 $this->resourceFactory( $args{resourceFactory} || ResourceClass );
28 25
29 my $resources = $args{resources} || []; 26 my $resources = $args{resources} || [];
30 my $operations = $args{operations} || {}; 27 my $verbs = $args{verbs} || {};
31 28
32 die ArgumentException->new(resources => 'resources parameter must be a reference to an array') 29 die ArgumentException->new(
33 unless ref $resources eq 'ARRAY'; 30 resources => 'resources parameter must be a reference to an array' )
34 31 unless ref $resources eq 'ARRAY';
35 die ArgumentException->new(opearations => 'operations parameter must be a reference to a hash') 32
36 unless ref $operations eq 'HASH'; 33 die ArgumentException->new(
37 34 opearations => 'operations parameter must be a reference to a hash' )
35 unless ref $verbs eq 'HASH';
36
37 $this->verbs(
38 { map { lc($_), $verbs->{$_} } keys %$verbs } );
39
38 my %nameMap; 40 my %nameMap;
39 41
40 foreach my $res (@$resources) { 42 foreach my $res (@$verbs) {
41 next unless $res->{contract}; 43 next unless $res->{contract};
42 if(my $name = $res->{name}) { 44 if ( my $name = $res->{name} ) {
43 $nameMap{$name} = $res; 45 $nameMap{$name} = $res;
44 } 46 }
45 if($res->{match}) { 47 if ( $res->{match} ) {
46 $this->_regexpResources->Append($res); 48 $this->_regexpResources->Append($res);
47 } 49 }
48 } 50 }
49 51
50 $this->_namedResources(\%nameMap); 52 $this->_namedResources( \%nameMap );
53 }
54
55 sub AddChildResourceContract {
56 my ($this,$res) = @_;
57
58 die ArgumentException->new(res => "A valid child resource definition")
59 unless ref $res eq 'HASH';
60
61 $this->_namedResources->{$res->{name}} = $res if $res->{name};
62 $this->_regexpResources->Append($res) if $res->{match};
63
64 return;
51 } 65 }
52 66
53 sub CreateResource { 67 sub CreateResource {
54 my $this = shift; 68 my $this = shift;
55 my %args = @_; 69 my %args = @_;
56 70
57 return $this->resourceFactory->new ( 71 return $this->resourceFactory->new( %args, contract => $this );
58 %args, 72 }
59 contract => $this 73
60 ); 74 sub FindChildResourceInfo {
61 } 75 my ( $this, $name ) = @_;
62 76
63 sub FindChildResourceContractInfo { 77 if ( my $info = $this->_namedResources->{$name} ) {
64 my ($this,$name) = @_; 78 return $info, [$name];
65 79 }
66 if(my $contract = $this->_namedResources->{$name}) { 80 else {
67 return $contract;
68 } else {
69 foreach my $info ( $this->_regexpResources ) { 81 foreach my $info ( $this->_regexpResources ) {
70 my $rx = $info->{match}; 82 my $rx = $info->{match};
71 return $info if $name =~ m/$rx/; 83 if(my @childId = $name =~ m/$rx/) {
84 return $info, \@childId;
85 }
72 } 86 }
73 } 87 }
74 88
75 return undef; 89 return;
76 }
77
78 sub CreateChildResource {
79 my $this = @_;
80 my %args = @_;
81
82 my $id = $args{id} or die ArgumentException( id => 'id parameter must be specified');
83 my $parent = $args{parent};
84 my $model = $parent->model if $parent;
85 my $binding, $childContract, @bindingVars;
86
87 if(my $info = $this->_namedResources->{$id}) {
88 @bindingVars = ($id);
89 $childContract = $info->{contract};
90 $binding = $info->{bind};
91 } else {
92 foreach my $info ( $this->_regexpResources ) {
93 my $rx = $info->{match};
94 next unless $rx;
95 if( @bindingVars = ($id =~ m/$rx/) ) {
96 $childContract = $info->{contract};
97 $binding = $info->{bind};
98 }
99 }
100 }
101
102 if ($childContract) {
103 my $childModel = $binding ? $binding->($parent,$model,@bindingVars) : undef;
104
105 return $childContract->CreateResource(
106 %args,
107 model => $childModel
108 );
109 } else {
110 die KeyNotFoundException->new($id);
111 }
112 } 90 }
113 91
114 1; 92 1;
115 93
116 __END__ 94 __END__
129 ResourceContract => 'IMPL::Web::Application::ResourceContract', 107 ResourceContract => 'IMPL::Web::Application::ResourceContract',
130 OperationContract => 'IMPL::Web::Application::OperationContract' 108 OperationContract => 'IMPL::Web::Application::OperationContract'
131 }; 109 };
132 110
133 my $contract = ResourceContract->new( 111 my $contract = ResourceContract->new(
134 operations => { 112 verbs => {
135 get => OperationContract->new( 113 get => OperationContract->new(
136 bind => sub { 114 binding => sub {
115 my ($resource,$action) = @_;
137 return "Hello!"; 116 return "Hello!";
138 } 117 }
139 ) 118 ),
119 post => OperationContract->new(
120 parameters => [
121 IMPL::Transform::DataToModel->new() # создаем преобразование для формы
122 ],
123 binding => sub {
124 my ($resource,$action,$data) = @_;
125 return $resource->model->AddItem($data);
126 },
127 success => sub {
128 my ($resource,$action,$result) = @_;
129 return IMPL::Web::HttpResponse->Redirect(
130 location => $resource->location->Child($result->id)
131 )
132 },
133 error => sub {
134 my ($resource,$action,$error) = @_;
135 $action->errors->Append($error);
136 return $resource->model;
137 }
138
139 ),
140 }, 140 },
141 resources => [ 141 resources => [
142 { 142 {
143 name => 'info', 143 name => 'info',
144 bind => sub { 144 binding => sub {
145 return $_[0]->model->info; 145 return $_[0]->model->info;
146 }, 146 },
147 contract => ResourceContract->new( 147 contract => ResourceContract->new(
148 get => OperationContract->new( 148 verbs => {
149 bind => sub { 149 get => OperationContract->new(
150 my ($resource,$model) = @_; 150 binding => sub {
151 return $model; # or the same: $resource->model; 151 my ($resource,$action) = @_;
152 } 152 return $resource->model;
153 ) 153 }
154 )
155 }
154 ) 156 )
155 } 157 }
156 ] 158 ]
157 ) 159 )
158 160
162 model => $obj, 164 model => $obj,
163 parent => $prentResource, 165 parent => $prentResource,
164 id => 'item-something' 166 id => 'item-something'
165 ); 167 );
166 168
167 my $child = $contract->CreateChildResource( 169 my $child = $contract->FetchChildResource('info');
168 parent => $resource,
169 id => 'info'
170 );
171 170
172 =end code 171 =end code
173 172
174 =head1 DESCRIPTION 173 =head1 DESCRIPTION
175 174
175 Контракт описывает структуру Веб-ресурса и отображение операций над ним в методы
176 предметной области. Контракты используются инфраструктурой и пользователь
177 не использует их напрямую, до тех пор пока не требуется изменить стандартный
178 функционал.
179
180
181 Ресурс представляе собой набор контрактов операций, соответствующих методам
182 C<HTTP> которые доступны у данного ресурса. Кроме операций ресурс состоит из
183 дочерних ресурсов, которые могут соответствовать регулярным выражениям, либо
184 иметь фиксированные имена.
185
186 Каждая операция над ресурсом C<IMPL::Web::Application::OperationContract>
187 описывается преобразованием параметров, привязкой к предметной области,
188 дополнительным обработчиком результата выполнения привязки, либо обработчиком
189 исключения, если привязку не удалось выполнить.
190
191 Основное назначение контракта - создавать объекты ресурсов, над которыми
192 контроллер запросов C<HTTP> сможет выполнить операцию. Контракт может создавать
193 дочерние ресурсы, на основе указанного родительского ресурса и идетификатора
194 нового ресурса. При этом будет найден подходящий контракт для дочернего ресурса
195 и с его помощью создан дочерний ресурс.
196
197 =head2 Динамический контракт
198
199 Основная функция контракта - превращать данные модели предметной области в
200 данные ресурсной модели, тоесть в ресурсы, для чего каждый контракт обязан
201 реализовывать метод C<CreateResource(%args)>.
202
203 Результатом выполнения этого метода должен быть Web-ресурс, см.
204 C<IMPL::Web::Application::Resource>. Другими словами не существует жесткого
205 требования к реализации самого контракта, как и того, что созданный ресурс
206 должен ссылаться именно на этот контракт (да и вообще ссылаться на контракт).
207
208 Таким образом можно реализовать контракт, который выполняет роль посредника,
209 ниже приведен пример, который выбирает нужный контракт на основе типа модели
210 переданной для создания ресурса.
211
212 =begin code
213
214 package My::Web::Application::ContractMapper;
215 use strict;
216 use IMPL::lang qw(:constants);
217 use IMPL::declare {
218 require => {
219 ForbiddenException => 'IMPL::Web::Forbidden'
220 },
221 base => [
222 'IMPL::Object' => undef,
223 'IMPL::Object::Autofill' => '@_'
224 ],
225 props => [
226 map => PROP_GET | PROP_OWNERSET
227 ]
228 }
229
230 sub CreateResource {
231 my ($this,%args) = @_;
232
233 my $type = ref $args{model} || '_default';
234
235 my $contract = $this->map->{$type};
236
237 die ForbiddenException->new()
238 unless $contract;
239
240 return $contract->CreateResource(%args);
241 }
242
243 =end code
244
245 =head1 MEMBERS
246
247 =head2 C<CTOR(%args)>
248
249 =over
250
251 =item * C<resourceFactory>
252
253 Фабрика объектов C<IMPL::Object::Factory> которая будет использоваться при
254 создании новых ресурсов. По-умолчанию C<IMPL::Web::Application::Resource>.
255
256 =item * C<operations>
257
258 Хеш с доступными действиями над C<HTTP> ресурсом, ключом является имя ресурса,
259 значением C<IMPL::Web::Application::OperationContract>.
260
261 =item * C<resources>
262
263 Ссылка на массив хешей, каждый хеш описывает правила, как получить дочерний
264 ресурс и связать его с контрактом. Ниже преведено описание элементов хеша.
265
266 =over
267
268 =item * C<name>
269
270 Имя дочернегно ресурса.
271
272 =item * C<match>
273
274 Регулярное выражение, которому должно удовлетворять имя дочернего ресурса.
275
276 =item * C<bind>
277
278 Делегат, получающий модель для дочернего ресурса. Первым параметром ему
279 передается родительский объект, далее передаются граппы из регулярного
280 выражения, если это ресурс с именем удовлетворяющим регулярному выражению из
281 элемента C<match>, либо имя ресурса, если это ресурс с именем, указанным в
282 элементе C<name>.
283
284 =item * C<contract>
285
286 Ссылка на C<IMPL::Web::Application::ResourceContract> для дочернего ресурса.
287 У данного контракта используется только метод C<CreateContract>.
288
289 =back
290
291 По крайней мере C<name> или C<match> должны присутсвовать.
292
293 =back
294
295 =head2 C<CreateResource(%args)>
296
297 Создает ресурс, параметры C<%args> будут переданы напрямую констркутору
298 ресурса, для создания ресурса используется фабрика C<resourceFactory>.
299 При создании, конгструктору ресурса, будет передана ссылка на текущй контракт.
300
301 По-сути никакого дополнительного функционала данный метод не несет.
302
303 =head2 C<FindChildResourceInfo($childId)>
304
305 Используется для поиска информации о дочернем ресурсе, возвращает список из двух
306 элементов. C<($info,$childIdParts)>
307
308 =over
309
310 =item * C<$info>
311
312 Информация о контракте дочернего ресурса, как правило это ссылка на хеш, похожий
313 по формату на
314
315 =back
316
317 =head2 C<[get]verbs>
318
319 Хеш с доступными действиями над C<HTTP> ресурсом, все имена операций приведены
320 к нижнему регистру.
321
322 =begin code
323
324 my $result = $contract->verbs->{get}->Invoke($resource,$action);
325
326 =end code
327
176 =cut 328 =cut