Mercurial > hg > orthanc
annotate Core/RestApi/RestApiHierarchy.cpp @ 1486:f967bdf8534e
refactoring to Logging.h
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 04 Aug 2015 10:01:31 +0200 |
parents | b2b09a3dbd8e |
children | 0a2ad4a6858f |
rev | line source |
---|---|
969 | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
1288
6e7e5ed91c2d
upgrade to year 2015
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1063
diff
changeset
|
3 * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics |
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 | |
33 #include "RestApiHierarchy.h" | |
34 | |
975 | 35 #include "../OrthancException.h" |
36 | |
969 | 37 #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
|
38 #include <stdio.h> |
969 | 39 |
40 namespace Orthanc | |
41 { | |
978 | 42 RestApiHierarchy::Resource::Resource() : |
972 | 43 getHandler_(NULL), |
44 postHandler_(NULL), | |
45 putHandler_(NULL), | |
46 deleteHandler_(NULL) | |
47 { | |
48 } | |
49 | |
50 | |
978 | 51 bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const |
972 | 52 { |
53 switch (method) | |
54 { | |
55 case HttpMethod_Get: | |
56 return getHandler_ != NULL; | |
57 | |
58 case HttpMethod_Post: | |
59 return postHandler_ != NULL; | |
60 | |
61 case HttpMethod_Put: | |
62 return putHandler_ != NULL; | |
63 | |
64 case HttpMethod_Delete: | |
65 return deleteHandler_ != NULL; | |
66 | |
67 default: | |
68 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
69 } | |
70 } | |
71 | |
72 | |
978 | 73 bool RestApiHierarchy::Resource::IsEmpty() const |
969 | 74 { |
972 | 75 return (getHandler_ == NULL && |
76 postHandler_ == NULL && | |
77 putHandler_ == NULL && | |
78 deleteHandler_ == NULL); | |
79 } | |
80 | |
81 | |
969 | 82 RestApiHierarchy& RestApiHierarchy::AddChild(Children& children, |
83 const std::string& name) | |
84 { | |
85 Children::iterator it = children.find(name); | |
86 | |
87 if (it == children.end()) | |
88 { | |
89 // Create new child | |
90 RestApiHierarchy *child = new RestApiHierarchy; | |
91 children[name] = child; | |
92 return *child; | |
93 } | |
94 else | |
95 { | |
96 return *it->second; | |
97 } | |
98 } | |
99 | |
100 | |
978 | 101 |
102 bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const | |
103 { | |
104 if (getHandler_ != NULL) | |
105 { | |
106 getHandler_(call); | |
107 return true; | |
108 } | |
109 else | |
110 { | |
111 return false; | |
112 } | |
113 } | |
114 | |
115 | |
116 bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const | |
117 { | |
118 if (putHandler_ != NULL) | |
119 { | |
120 putHandler_(call); | |
121 return true; | |
122 } | |
123 else | |
124 { | |
125 return false; | |
126 } | |
127 } | |
128 | |
129 | |
130 bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const | |
131 { | |
132 if (postHandler_ != NULL) | |
133 { | |
134 postHandler_(call); | |
135 return true; | |
136 } | |
137 else | |
138 { | |
139 return false; | |
140 } | |
141 } | |
142 | |
143 | |
144 bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const | |
145 { | |
146 if (deleteHandler_ != NULL) | |
147 { | |
148 deleteHandler_(call); | |
149 return true; | |
150 } | |
151 else | |
152 { | |
153 return false; | |
154 } | |
155 } | |
156 | |
157 | |
158 | |
969 | 159 void RestApiHierarchy::DeleteChildren(Children& children) |
160 { | |
161 for (Children::iterator it = children.begin(); | |
1303 | 162 it != children.end(); ++it) |
969 | 163 { |
164 delete it->second; | |
165 } | |
166 } | |
167 | |
168 | |
169 template <typename Handler> | |
170 void RestApiHierarchy::RegisterInternal(const RestApiPath& path, | |
171 Handler handler, | |
172 size_t level) | |
173 { | |
174 if (path.GetLevelCount() == level) | |
175 { | |
176 if (path.IsUniversalTrailing()) | |
177 { | |
178 universalHandlers_.Register(handler); | |
179 } | |
180 else | |
181 { | |
182 handlers_.Register(handler); | |
183 } | |
184 } | |
185 else | |
186 { | |
187 RestApiHierarchy* child; | |
188 if (path.IsWildcardLevel(level)) | |
189 { | |
190 child = &AddChild(wildcardChildren_, path.GetWildcardName(level)); | |
191 } | |
192 else | |
193 { | |
194 child = &AddChild(children_, path.GetLevelName(level)); | |
195 } | |
196 | |
197 child->RegisterInternal(path, handler, level + 1); | |
198 } | |
199 } | |
200 | |
201 | |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
202 bool RestApiHierarchy::LookupResource(IHttpHandler::Arguments& components, |
969 | 203 const UriComponents& uri, |
978 | 204 IVisitor& visitor, |
205 size_t level) | |
969 | 206 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
207 if (uri.size() != 0 && |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
208 level > uri.size()) |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
209 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
210 return false; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
211 } |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
212 |
969 | 213 UriComponents trailing; |
214 | |
215 // 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
|
216 if (uri.size() == 0 || |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
217 level == uri.size()) |
969 | 218 { |
219 if (!handlers_.IsEmpty() && | |
978 | 220 visitor.Visit(handlers_, uri, components, trailing)) |
969 | 221 { |
222 return true; | |
223 } | |
224 } | |
225 | |
226 | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
227 if (level < uri.size()) // A recursive call is possible |
969 | 228 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
229 // 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
|
230 Children::const_iterator child = children_.find(uri[level]); |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
231 if (child != children_.end()) |
969 | 232 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
233 if (child->second->LookupResource(components, uri, visitor, level + 1)) |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
234 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
235 return true; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
236 } |
969 | 237 } |
238 | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
239 // 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
|
240 for (child = wildcardChildren_.begin(); |
1303 | 241 child != wildcardChildren_.end(); ++child) |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
242 { |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
243 IHttpHandler::Arguments subComponents = components; |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
244 subComponents[child->first] = uri[level]; |
969 | 245 |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
246 if (child->second->LookupResource(subComponents, uri, visitor, level + 1)) |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
247 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
248 return true; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
249 } |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
250 } |
969 | 251 } |
252 | |
253 | |
254 // As a last resort, call the universal handlers, if any | |
255 if (!universalHandlers_.IsEmpty()) | |
256 { | |
257 trailing.resize(uri.size() - level); | |
258 size_t pos = 0; | |
259 for (size_t i = level; i < uri.size(); i++, pos++) | |
260 { | |
261 trailing[pos] = uri[i]; | |
262 } | |
263 | |
264 assert(pos == trailing.size()); | |
265 | |
978 | 266 if (visitor.Visit(universalHandlers_, uri, components, trailing)) |
969 | 267 { |
268 return true; | |
269 } | |
270 } | |
271 | |
272 return false; | |
273 } | |
274 | |
275 | |
978 | 276 bool RestApiHierarchy::CanGenerateDirectory() const |
277 { | |
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
|
278 return (universalHandlers_.IsEmpty() && |
1303 | 279 wildcardChildren_.empty()); |
978 | 280 } |
281 | |
282 | |
969 | 283 bool RestApiHierarchy::GetDirectory(Json::Value& result, |
284 const UriComponents& uri, | |
285 size_t level) | |
286 { | |
287 if (uri.size() == level) | |
288 { | |
978 | 289 if (CanGenerateDirectory()) |
969 | 290 { |
291 result = Json::arrayValue; | |
292 | |
293 for (Children::const_iterator it = children_.begin(); | |
1303 | 294 it != children_.end(); ++it) |
969 | 295 { |
296 result.append(it->first); | |
297 } | |
298 | |
299 return true; | |
300 } | |
301 else | |
302 { | |
303 return false; | |
304 } | |
305 } | |
306 | |
307 Children::const_iterator child = children_.find(uri[level]); | |
308 if (child != children_.end()) | |
309 { | |
310 if (child->second->GetDirectory(result, uri, level + 1)) | |
311 { | |
312 return true; | |
313 } | |
314 } | |
315 | |
316 for (child = wildcardChildren_.begin(); | |
1303 | 317 child != wildcardChildren_.end(); ++child) |
969 | 318 { |
319 if (child->second->GetDirectory(result, uri, level + 1)) | |
320 { | |
321 return true; | |
322 } | |
323 } | |
324 | |
325 return false; | |
326 } | |
327 | |
328 | |
329 RestApiHierarchy::~RestApiHierarchy() | |
330 { | |
331 DeleteChildren(children_); | |
332 DeleteChildren(wildcardChildren_); | |
333 } | |
334 | |
970 | 335 void RestApiHierarchy::Register(const std::string& uri, |
974 | 336 RestApiGetCall::Handler handler) |
969 | 337 { |
970 | 338 RestApiPath path(uri); |
969 | 339 RegisterInternal(path, handler, 0); |
340 } | |
341 | |
970 | 342 void RestApiHierarchy::Register(const std::string& uri, |
974 | 343 RestApiPutCall::Handler handler) |
969 | 344 { |
970 | 345 RestApiPath path(uri); |
969 | 346 RegisterInternal(path, handler, 0); |
347 } | |
348 | |
970 | 349 void RestApiHierarchy::Register(const std::string& uri, |
974 | 350 RestApiPostCall::Handler handler) |
969 | 351 { |
970 | 352 RestApiPath path(uri); |
969 | 353 RegisterInternal(path, handler, 0); |
354 } | |
355 | |
970 | 356 void RestApiHierarchy::Register(const std::string& uri, |
974 | 357 RestApiDeleteCall::Handler handler) |
969 | 358 { |
970 | 359 RestApiPath path(uri); |
969 | 360 RegisterInternal(path, handler, 0); |
361 } | |
362 | |
363 void RestApiHierarchy::CreateSiteMap(Json::Value& target) const | |
364 { | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
365 target = Json::objectValue; |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
366 |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
367 /*std::string s = " "; |
972 | 368 if (handlers_.HasHandler(HttpMethod_Get)) |
969 | 369 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
370 s += "GET "; |
969 | 371 } |
372 | |
972 | 373 if (handlers_.HasHandler(HttpMethod_Post)) |
969 | 374 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
375 s += "POST "; |
969 | 376 } |
377 | |
972 | 378 if (handlers_.HasHandler(HttpMethod_Put)) |
969 | 379 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
380 s += "PUT "; |
969 | 381 } |
382 | |
972 | 383 if (handlers_.HasHandler(HttpMethod_Delete)) |
969 | 384 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
385 s += "DELETE "; |
969 | 386 } |
387 | |
978 | 388 target = s;*/ |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
389 |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
390 for (Children::const_iterator it = children_.begin(); |
1303 | 391 it != children_.end(); ++it) |
969 | 392 { |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
393 it->second->CreateSiteMap(target[it->first]); |
969 | 394 } |
395 | |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
396 for (Children::const_iterator it = wildcardChildren_.begin(); |
1303 | 397 it != wildcardChildren_.end(); ++it) |
980
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
398 { |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
399 it->second->CreateSiteMap(target["<" + it->first + ">"]); |
f1ff2a2f06cd
use RestApiHierarchy inside RestApi
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
978
diff
changeset
|
400 } |
969 | 401 } |
402 | |
978 | 403 |
404 bool RestApiHierarchy::LookupResource(const UriComponents& uri, | |
405 IVisitor& visitor) | |
969 | 406 { |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
407 IHttpHandler::Arguments components; |
978 | 408 return LookupResource(components, uri, visitor, 0); |
969 | 409 } |
410 | |
978 | 411 |
412 | |
413 namespace | |
414 { | |
415 // Anonymous namespace to avoid clashes between compilation modules | |
416 | |
417 class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor | |
418 { | |
419 private: | |
420 std::set<HttpMethod>& methods_; | |
421 | |
422 public: | |
423 AcceptedMethodsVisitor(std::set<HttpMethod>& methods) : methods_(methods) | |
424 { | |
425 } | |
426 | |
427 virtual bool Visit(const RestApiHierarchy::Resource& resource, | |
428 const UriComponents& uri, | |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
429 const IHttpHandler::Arguments& components, |
978 | 430 const UriComponents& trailing) |
431 { | |
432 if (trailing.size() == 0) // Ignore universal handlers | |
433 { | |
434 if (resource.HasHandler(HttpMethod_Get)) | |
435 { | |
436 methods_.insert(HttpMethod_Get); | |
437 } | |
438 | |
439 if (resource.HasHandler(HttpMethod_Post)) | |
440 { | |
441 methods_.insert(HttpMethod_Post); | |
442 } | |
443 | |
444 if (resource.HasHandler(HttpMethod_Put)) | |
445 { | |
446 methods_.insert(HttpMethod_Put); | |
447 } | |
448 | |
449 if (resource.HasHandler(HttpMethod_Delete)) | |
450 { | |
451 methods_.insert(HttpMethod_Delete); | |
452 } | |
453 } | |
454 | |
455 return false; // Continue to check all the possible ways to access this URI | |
456 } | |
457 }; | |
458 } | |
459 | |
460 void RestApiHierarchy::GetAcceptedMethods(std::set<HttpMethod>& methods, | |
461 const UriComponents& uri) | |
969 | 462 { |
1441
f3672356c121
refactoring: IHttpHandler and HttpToolbox
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1303
diff
changeset
|
463 IHttpHandler::Arguments components; |
978 | 464 AcceptedMethodsVisitor visitor(methods); |
1444
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
465 if (LookupResource(components, uri, visitor, 0)) |
978 | 466 { |
1444
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
467 Json::Value d; |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
468 if (GetDirectory(d, uri)) |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
469 { |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
470 methods.insert(HttpMethod_Get); |
b2b09a3dbd8e
refactoring: OrthancHttpHandler inside OrthancServer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
1441
diff
changeset
|
471 } |
978 | 472 } |
473 } | |
969 | 474 } |