Mercurial > hg > orthanc
annotate Core/RestApi/RestApiHierarchy.cpp @ 2228:577888e9ed2f
fix mainline version
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 13 Dec 2016 17:42:06 +0100 |
parents | b1291df2f780 |
children | a3a65de1840f |
rev | line source |
---|---|
969 | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
1900 | 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics |
1288
6e7e5ed91c2d
upgrade to year 2015
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1063
diff
changeset
|
4 * Department, University Hospital of Liege, Belgium |
969 | 5 * |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU General Public License as | |
8 * published by the Free Software Foundation, either version 3 of the | |
9 * License, or (at your option) any later version. | |
10 * | |
11 * In addition, as a special exception, the copyright holders of this | |
12 * program give permission to link the code of its release with the | |
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
14 * that use the same license as the "OpenSSL" library), and distribute | |
15 * the linked executables. You must obey the GNU General Public License | |
16 * in all respects for all of the code used other than "OpenSSL". If you | |
17 * modify file(s) with this exception, you may extend this exception to | |
18 * your version of the file(s), but you are not obligated to do so. If | |
19 * you do not wish to do so, delete this exception statement from your | |
20 * version. If you delete this exception statement from all source files | |
21 * in the program, then also delete it here. | |
22 * | |
23 * This program is distributed in the hope that it will be useful, but | |
24 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
26 * General Public License for more details. | |
27 * | |
28 * You should have received a copy of the GNU General Public License | |
29 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
30 **/ | |
31 | |
32 | |
1624
0a2ad4a6858f
fix missing precompiled headers
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1444
diff
changeset
|
33 #include "../PrecompiledHeaders.h" |
969 | 34 #include "RestApiHierarchy.h" |
35 | |
975 | 36 #include "../OrthancException.h" |
37 | |
969 | 38 #include <cassert> |
1063
0332e6e8c679
Fix automated generation of the list of resource children in the REST API
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
980
diff
changeset
|
39 #include <stdio.h> |
969 | 40 |
41 namespace Orthanc | |
42 { | |
978 | 43 RestApiHierarchy::Resource::Resource() : |
972 | 44 getHandler_(NULL), |
45 postHandler_(NULL), | |
46 putHandler_(NULL), | |
47 deleteHandler_(NULL) | |
48 { | |
49 } | |
50 | |
51 | |
978 | 52 bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const |
972 | 53 { |
54 switch (method) | |
55 { | |
56 case HttpMethod_Get: | |
57 return getHandler_ != NULL; | |
58 | |
59 case HttpMethod_Post: | |
60 return postHandler_ != NULL; | |
61 | |
62 case HttpMethod_Put: | |
63 return putHandler_ != NULL; | |
64 | |
65 case HttpMethod_Delete: | |
66 return deleteHandler_ != NULL; | |
67 | |
68 default: | |
69 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
70 } | |
71 } | |
72 | |
73 | |
978 | 74 bool RestApiHierarchy::Resource::IsEmpty() const |
969 | 75 { |
972 | 76 return (getHandler_ == NULL && |
77 postHandler_ == NULL && | |
78 putHandler_ == NULL && | |
79 deleteHandler_ == NULL); | |
80 } | |
81 | |
82 | |
969 | 83 RestApiHierarchy& RestApiHierarchy::AddChild(Children& children, |
84 const std::string& name) | |
85 { | |
86 Children::iterator it = children.find(name); | |
87 | |
88 if (it == children.end()) | |
89 { | |
90 // Create new child | |
91 RestApiHierarchy *child = new RestApiHierarchy; | |
92 children[name] = child; | |
93 return *child; | |
94 } | |
95 else | |
96 { | |
97 return *it->second; | |
98 } | |
99 } | |
100 | |
101 | |
978 | 102 |
103 bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const | |
104 { | |
105 if (getHandler_ != NULL) | |
106 { | |
107 getHandler_(call); | |
108 return true; | |
109 } | |
110 else | |
111 { | |
112 return false; | |
113 } | |
114 } | |
115 | |
116 | |
117 bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const | |
118 { | |
119 if (putHandler_ != NULL) | |
120 { | |
121 putHandler_(call); | |
122 return true; | |
123 } | |
124 else | |
125 { | |
126 return false; | |
127 } | |
128 } | |
129 | |
130 | |
131 bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const | |
132 { | |
133 if (postHandler_ != NULL) | |
134 { | |
135 postHandler_(call); | |
136 return true; | |
137 } | |
138 else | |
139 { | |
140 return false; | |
141 } | |
142 } | |
143 | |
144 | |
145 bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const | |
146 { | |
147 if (deleteHandler_ != NULL) | |
148 { | |
149 deleteHandler_(call); | |
150 return true; | |
151 } | |
152 else | |
153 { | |
154 return false; | |
155 } | |
156 } | |
157 | |
158 | |
159 | |
969 | 160 void RestApiHierarchy::DeleteChildren(Children& children) |
161 { | |
162 for (Children::iterator it = children.begin(); | |
1303 | 163 it != children.end(); ++it) |
969 | 164 { |
165 delete it->second; | |
166 } | |
167 } | |
168 | |
169 | |
170 template <typename Handler> | |
171 void RestApiHierarchy::RegisterInternal(const RestApiPath& path, | |
172 Handler handler, | |
173 size_t level) | |
174 { | |
175 if (path.GetLevelCount() == level) | |
176 { | |
177 if (path.IsUniversalTrailing()) | |
178 { | |
179 universalHandlers_.Register(handler); | |
180 } | |
181 else | |
182 { | |
183 handlers_.Register(handler); | |
184 } | |
185 } | |
186 else | |
187 { | |
188 RestApiHierarchy* child; | |
189 if (path.IsWildcardLevel(level)) | |
190 { | |
191 child = &AddChild(wildcardChildren_, path.GetWildcardName(level)); | |
192 } | |
193 else | |
194 { | |
195 child = &AddChild(children_, path.GetLevelName(level)); | |
196 } | |
197 | |
198 child->RegisterInternal(path, handler, level + 1); | |
199 } | |
200 } | |
201 | |
202 | |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
203 bool RestApiHierarchy::LookupResource(IHttpHandler::Arguments& components, |
969 | 204 const UriComponents& uri, |
978 | 205 IVisitor& visitor, |
206 size_t level) | |
969 | 207 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
208 if (uri.size() != 0 && |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
209 level > uri.size()) |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
210 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
211 return false; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
212 } |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
213 |
969 | 214 UriComponents trailing; |
215 | |
216 // Look for an exact match on the resource of interest | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
217 if (uri.size() == 0 || |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
218 level == uri.size()) |
969 | 219 { |
220 if (!handlers_.IsEmpty() && | |
978 | 221 visitor.Visit(handlers_, uri, components, trailing)) |
969 | 222 { |
223 return true; | |
224 } | |
225 } | |
226 | |
227 | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
228 if (level < uri.size()) // A recursive call is possible |
969 | 229 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
230 // Try and go down in the hierarchy, using an exact match for the child |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
231 Children::const_iterator child = children_.find(uri[level]); |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
232 if (child != children_.end()) |
969 | 233 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
234 if (child->second->LookupResource(components, uri, visitor, level + 1)) |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
235 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
236 return true; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
237 } |
969 | 238 } |
239 | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
240 // Try and go down in the hierarchy, using wildcard rules for children |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
241 for (child = wildcardChildren_.begin(); |
1303 | 242 child != wildcardChildren_.end(); ++child) |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
243 { |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
244 IHttpHandler::Arguments subComponents = components; |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
245 subComponents[child->first] = uri[level]; |
969 | 246 |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
247 if (child->second->LookupResource(subComponents, uri, visitor, level + 1)) |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
248 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
249 return true; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
250 } |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
251 } |
969 | 252 } |
253 | |
254 | |
255 // As a last resort, call the universal handlers, if any | |
256 if (!universalHandlers_.IsEmpty()) | |
257 { | |
258 trailing.resize(uri.size() - level); | |
259 size_t pos = 0; | |
260 for (size_t i = level; i < uri.size(); i++, pos++) | |
261 { | |
262 trailing[pos] = uri[i]; | |
263 } | |
264 | |
265 assert(pos == trailing.size()); | |
266 | |
978 | 267 if (visitor.Visit(universalHandlers_, uri, components, trailing)) |
969 | 268 { |
269 return true; | |
270 } | |
271 } | |
272 | |
273 return false; | |
274 } | |
275 | |
276 | |
978 | 277 bool RestApiHierarchy::CanGenerateDirectory() const |
278 { | |
1063
0332e6e8c679
Fix automated generation of the list of resource children in the REST API
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
980
diff
changeset
|
279 return (universalHandlers_.IsEmpty() && |
1303 | 280 wildcardChildren_.empty()); |
978 | 281 } |
282 | |
283 | |
969 | 284 bool RestApiHierarchy::GetDirectory(Json::Value& result, |
285 const UriComponents& uri, | |
286 size_t level) | |
287 { | |
288 if (uri.size() == level) | |
289 { | |
978 | 290 if (CanGenerateDirectory()) |
969 | 291 { |
292 result = Json::arrayValue; | |
293 | |
294 for (Children::const_iterator it = children_.begin(); | |
1303 | 295 it != children_.end(); ++it) |
969 | 296 { |
297 result.append(it->first); | |
298 } | |
299 | |
300 return true; | |
301 } | |
302 else | |
303 { | |
304 return false; | |
305 } | |
306 } | |
307 | |
308 Children::const_iterator child = children_.find(uri[level]); | |
309 if (child != children_.end()) | |
310 { | |
311 if (child->second->GetDirectory(result, uri, level + 1)) | |
312 { | |
313 return true; | |
314 } | |
315 } | |
316 | |
317 for (child = wildcardChildren_.begin(); | |
1303 | 318 child != wildcardChildren_.end(); ++child) |
969 | 319 { |
320 if (child->second->GetDirectory(result, uri, level + 1)) | |
321 { | |
322 return true; | |
323 } | |
324 } | |
325 | |
326 return false; | |
327 } | |
328 | |
329 | |
330 RestApiHierarchy::~RestApiHierarchy() | |
331 { | |
332 DeleteChildren(children_); | |
333 DeleteChildren(wildcardChildren_); | |
334 } | |
335 | |
970 | 336 void RestApiHierarchy::Register(const std::string& uri, |
974 | 337 RestApiGetCall::Handler handler) |
969 | 338 { |
970 | 339 RestApiPath path(uri); |
969 | 340 RegisterInternal(path, handler, 0); |
341 } | |
342 | |
970 | 343 void RestApiHierarchy::Register(const std::string& uri, |
974 | 344 RestApiPutCall::Handler handler) |
969 | 345 { |
970 | 346 RestApiPath path(uri); |
969 | 347 RegisterInternal(path, handler, 0); |
348 } | |
349 | |
970 | 350 void RestApiHierarchy::Register(const std::string& uri, |
974 | 351 RestApiPostCall::Handler handler) |
969 | 352 { |
970 | 353 RestApiPath path(uri); |
969 | 354 RegisterInternal(path, handler, 0); |
355 } | |
356 | |
970 | 357 void RestApiHierarchy::Register(const std::string& uri, |
974 | 358 RestApiDeleteCall::Handler handler) |
969 | 359 { |
970 | 360 RestApiPath path(uri); |
969 | 361 RegisterInternal(path, handler, 0); |
362 } | |
363 | |
364 void RestApiHierarchy::CreateSiteMap(Json::Value& target) const | |
365 { | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
366 target = Json::objectValue; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
367 |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
368 /*std::string s = " "; |
972 | 369 if (handlers_.HasHandler(HttpMethod_Get)) |
969 | 370 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
371 s += "GET "; |
969 | 372 } |
373 | |
972 | 374 if (handlers_.HasHandler(HttpMethod_Post)) |
969 | 375 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
376 s += "POST "; |
969 | 377 } |
378 | |
972 | 379 if (handlers_.HasHandler(HttpMethod_Put)) |
969 | 380 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
381 s += "PUT "; |
969 | 382 } |
383 | |
972 | 384 if (handlers_.HasHandler(HttpMethod_Delete)) |
969 | 385 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
386 s += "DELETE "; |
969 | 387 } |
388 | |
978 | 389 target = s;*/ |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
390 |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
391 for (Children::const_iterator it = children_.begin(); |
1303 | 392 it != children_.end(); ++it) |
969 | 393 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
394 it->second->CreateSiteMap(target[it->first]); |
969 | 395 } |
396 | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
397 for (Children::const_iterator it = wildcardChildren_.begin(); |
1303 | 398 it != wildcardChildren_.end(); ++it) |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
399 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
400 it->second->CreateSiteMap(target["<" + it->first + ">"]); |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
401 } |
969 | 402 } |
403 | |
978 | 404 |
405 bool RestApiHierarchy::LookupResource(const UriComponents& uri, | |
406 IVisitor& visitor) | |
969 | 407 { |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
408 IHttpHandler::Arguments components; |
978 | 409 return LookupResource(components, uri, visitor, 0); |
969 | 410 } |
411 | |
978 | 412 |
413 | |
414 namespace | |
415 { | |
416 // Anonymous namespace to avoid clashes between compilation modules | |
417 | |
418 class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor | |
419 { | |
420 private: | |
421 std::set<HttpMethod>& methods_; | |
422 | |
423 public: | |
424 AcceptedMethodsVisitor(std::set<HttpMethod>& methods) : methods_(methods) | |
425 { | |
426 } | |
427 | |
428 virtual bool Visit(const RestApiHierarchy::Resource& resource, | |
429 const UriComponents& uri, | |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
430 const IHttpHandler::Arguments& components, |
978 | 431 const UriComponents& trailing) |
432 { | |
433 if (trailing.size() == 0) // Ignore universal handlers | |
434 { | |
435 if (resource.HasHandler(HttpMethod_Get)) | |
436 { | |
437 methods_.insert(HttpMethod_Get); | |
438 } | |
439 | |
440 if (resource.HasHandler(HttpMethod_Post)) | |
441 { | |
442 methods_.insert(HttpMethod_Post); | |
443 } | |
444 | |
445 if (resource.HasHandler(HttpMethod_Put)) | |
446 { | |
447 methods_.insert(HttpMethod_Put); | |
448 } | |
449 | |
450 if (resource.HasHandler(HttpMethod_Delete)) | |
451 { | |
452 methods_.insert(HttpMethod_Delete); | |
453 } | |
454 } | |
455 | |
456 return false; // Continue to check all the possible ways to access this URI | |
457 } | |
458 }; | |
459 } | |
460 | |
461 void RestApiHierarchy::GetAcceptedMethods(std::set<HttpMethod>& methods, | |
462 const UriComponents& uri) | |
969 | 463 { |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
464 IHttpHandler::Arguments components; |
978 | 465 AcceptedMethodsVisitor visitor(methods); |
1444
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
466 if (LookupResource(components, uri, visitor, 0)) |
978 | 467 { |
1444
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
468 Json::Value d; |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
469 if (GetDirectory(d, uri)) |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
470 { |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
471 methods.insert(HttpMethod_Get); |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
472 } |
978 | 473 } |
474 } | |
969 | 475 } |