Mercurial > hg > orthanc-stone
comparison Samples/Sdl/Loader.cpp @ 619:9cd19b28f011
test: refactoring oracle
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 07 May 2019 11:13:24 +0200 |
parents | |
children | 8a3a25f2d42c |
comparison
equal
deleted
inserted
replaced
618:0925b27e8750 | 619:9cd19b28f011 |
---|---|
1 /** | |
2 * Stone of Orthanc | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2019 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 // From Stone | |
22 #include "../../Framework/StoneInitialization.h" | |
23 #include "../../Framework/Messages/IMessage.h" | |
24 #include "../../Framework/Messages/MessageBroker.h" | |
25 #include "../../Framework/Messages/ICallable.h" | |
26 #include "../../Framework/Messages/IObservable.h" | |
27 | |
28 // From Orthanc framework | |
29 #include <Core/IDynamicObject.h> | |
30 #include <Core/Images/Image.h> | |
31 #include <Core/Images/ImageProcessing.h> | |
32 #include <Core/Images/PngWriter.h> | |
33 #include <Core/Logging.h> | |
34 #include <Core/HttpClient.h> | |
35 #include <Core/MultiThreading/SharedMessageQueue.h> | |
36 #include <Core/OrthancException.h> | |
37 | |
38 #include <json/reader.h> | |
39 #include <json/value.h> | |
40 #include <json/writer.h> | |
41 | |
42 #include <list> | |
43 #include <stdio.h> | |
44 | |
45 | |
46 | |
47 namespace Refactoring | |
48 { | |
49 class IOracleCommand : public boost::noncopyable | |
50 { | |
51 public: | |
52 enum Type | |
53 { | |
54 Type_OrthancApi | |
55 }; | |
56 | |
57 virtual ~IOracleCommand() | |
58 { | |
59 } | |
60 | |
61 virtual Type GetType() const = 0; | |
62 }; | |
63 | |
64 | |
65 class IOracle : public boost::noncopyable | |
66 { | |
67 public: | |
68 virtual ~IOracle() | |
69 { | |
70 } | |
71 | |
72 virtual void Schedule(IOracleCommand* command) = 0; // Takes ownership | |
73 }; | |
74 | |
75 | |
76 | |
77 | |
78 class OracleCommandWithPayload : public IOracleCommand | |
79 { | |
80 private: | |
81 std::auto_ptr<Orthanc::IDynamicObject> payload_; | |
82 | |
83 public: | |
84 void SetPayload(Orthanc::IDynamicObject* payload) | |
85 { | |
86 if (payload == NULL) | |
87 { | |
88 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
89 } | |
90 else | |
91 { | |
92 payload_.reset(payload); | |
93 } | |
94 } | |
95 | |
96 bool HasPayload() const | |
97 { | |
98 return (payload_.get() != NULL); | |
99 } | |
100 | |
101 const Orthanc::IDynamicObject& GetPayload() const | |
102 { | |
103 if (HasPayload()) | |
104 { | |
105 return *payload_; | |
106 } | |
107 else | |
108 { | |
109 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
110 } | |
111 } | |
112 }; | |
113 | |
114 | |
115 | |
116 typedef std::map<std::string, std::string> HttpHeaders; | |
117 | |
118 class OrthancApiOracleCommand : public OracleCommandWithPayload | |
119 { | |
120 public: | |
121 class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess, // TODO | |
122 OrthancApiOracleCommand> | |
123 { | |
124 private: | |
125 HttpHeaders headers_; | |
126 std::string answer_; | |
127 | |
128 public: | |
129 SuccessMessage(const OrthancApiOracleCommand& command, | |
130 const HttpHeaders& answerHeaders, | |
131 std::string& answer /* will be swapped to avoid a memcpy() */) : | |
132 OriginMessage(command), | |
133 headers_(answerHeaders), | |
134 answer_(answer) | |
135 { | |
136 } | |
137 | |
138 const std::string& GetAnswer() const | |
139 { | |
140 return answer_; | |
141 } | |
142 | |
143 void GetJsonBody(Json::Value& target) const | |
144 { | |
145 Json::Reader reader; | |
146 if (!reader.parse(answer_, target)) | |
147 { | |
148 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
149 } | |
150 } | |
151 | |
152 const HttpHeaders& GetAnswerHeaders() const | |
153 { | |
154 return headers_; | |
155 } | |
156 }; | |
157 | |
158 | |
159 class FailureMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestError, // TODO | |
160 OrthancApiOracleCommand> | |
161 { | |
162 private: | |
163 Orthanc::HttpStatus status_; | |
164 | |
165 public: | |
166 FailureMessage(const OrthancApiOracleCommand& command, | |
167 Orthanc::HttpStatus status) : | |
168 OriginMessage(command), | |
169 status_(status) | |
170 { | |
171 } | |
172 | |
173 Orthanc::HttpStatus GetHttpStatus() const | |
174 { | |
175 return status_; | |
176 } | |
177 }; | |
178 | |
179 | |
180 private: | |
181 Orthanc::HttpMethod method_; | |
182 std::string uri_; | |
183 std::string body_; | |
184 HttpHeaders headers_; | |
185 unsigned int timeout_; | |
186 | |
187 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; | |
188 std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> > failureCallback_; | |
189 | |
190 public: | |
191 OrthancApiOracleCommand() : | |
192 method_(Orthanc::HttpMethod_Get), | |
193 uri_("/"), | |
194 timeout_(10) | |
195 { | |
196 } | |
197 | |
198 virtual Type GetType() const | |
199 { | |
200 return Type_OrthancApi; | |
201 } | |
202 | |
203 void SetMethod(Orthanc::HttpMethod method) | |
204 { | |
205 method_ = method; | |
206 } | |
207 | |
208 void SetUri(const std::string& uri) | |
209 { | |
210 uri_ = uri; | |
211 } | |
212 | |
213 void SetBody(const std::string& body) | |
214 { | |
215 body_ = body; | |
216 } | |
217 | |
218 void SetBody(const Json::Value& json) | |
219 { | |
220 Json::FastWriter writer; | |
221 body_ = writer.write(json); | |
222 } | |
223 | |
224 void SetHttpHeader(const std::string& key, | |
225 const std::string& value) | |
226 { | |
227 headers_[key] = value; | |
228 } | |
229 | |
230 Orthanc::HttpMethod GetMethod() const | |
231 { | |
232 return method_; | |
233 } | |
234 | |
235 const std::string& GetUri() const | |
236 { | |
237 return uri_; | |
238 } | |
239 | |
240 const std::string& GetBody() const | |
241 { | |
242 if (method_ == Orthanc::HttpMethod_Post || | |
243 method_ == Orthanc::HttpMethod_Put) | |
244 { | |
245 return body_; | |
246 } | |
247 else | |
248 { | |
249 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
250 } | |
251 } | |
252 | |
253 const HttpHeaders& GetHttpHeaders() const | |
254 { | |
255 return headers_; | |
256 } | |
257 | |
258 void SetTimeout(unsigned int seconds) | |
259 { | |
260 timeout_ = seconds; | |
261 } | |
262 | |
263 unsigned int GetTimeout() const | |
264 { | |
265 return timeout_; | |
266 } | |
267 }; | |
268 | |
269 | |
270 | |
271 class NativeApplicationContext : public boost::noncopyable | |
272 { | |
273 private: | |
274 boost::shared_mutex mutex_; | |
275 Orthanc::WebServiceParameters orthanc_; | |
276 OrthancStone::MessageBroker broker_; | |
277 OrthancStone::IObservable oracleObservable_; | |
278 | |
279 public: | |
280 NativeApplicationContext() : | |
281 oracleObservable_(broker_) | |
282 { | |
283 orthanc_.SetUrl("http://localhost:8042/"); | |
284 } | |
285 | |
286 | |
287 class ReaderLock : public boost::noncopyable | |
288 { | |
289 private: | |
290 NativeApplicationContext& that_; | |
291 boost::shared_lock<boost::shared_mutex> lock_; | |
292 | |
293 public: | |
294 ReaderLock(NativeApplicationContext& that) : | |
295 that_(that), | |
296 lock_(that.mutex_) | |
297 { | |
298 } | |
299 | |
300 const Orthanc::WebServiceParameters& GetOrthancParameters() const | |
301 { | |
302 return that_.orthanc_; | |
303 } | |
304 }; | |
305 | |
306 | |
307 class WriterLock : public boost::noncopyable | |
308 { | |
309 private: | |
310 NativeApplicationContext& that_; | |
311 boost::unique_lock<boost::shared_mutex> lock_; | |
312 | |
313 public: | |
314 WriterLock(NativeApplicationContext& that) : | |
315 that_(that), | |
316 lock_(that.mutex_) | |
317 { | |
318 } | |
319 | |
320 OrthancStone::MessageBroker& GetBroker() | |
321 { | |
322 return that_.broker_; | |
323 } | |
324 | |
325 void SetOrthancParameters(Orthanc::WebServiceParameters& orthanc) | |
326 { | |
327 that_.orthanc_ = orthanc; | |
328 } | |
329 | |
330 OrthancStone::IObservable& GetOracleObservable() | |
331 { | |
332 return that_.oracleObservable_; | |
333 } | |
334 }; | |
335 }; | |
336 | |
337 | |
338 | |
339 class NativeOracle : public IOracle | |
340 { | |
341 private: | |
342 class Item : public Orthanc::IDynamicObject | |
343 { | |
344 private: | |
345 std::auto_ptr<IOracleCommand> command_; | |
346 | |
347 public: | |
348 Item(IOracleCommand* command) : | |
349 command_(command) | |
350 { | |
351 if (command == NULL) | |
352 { | |
353 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
354 } | |
355 } | |
356 | |
357 const IOracleCommand& GetCommand() | |
358 { | |
359 assert(command_.get() != NULL); | |
360 return *command_; | |
361 } | |
362 }; | |
363 | |
364 | |
365 enum State | |
366 { | |
367 State_Setup, | |
368 State_Running, | |
369 State_Stopped | |
370 }; | |
371 | |
372 | |
373 NativeApplicationContext& context_; | |
374 Orthanc::SharedMessageQueue queue_; | |
375 State state_; | |
376 boost::mutex mutex_; | |
377 std::vector<boost::thread*> workers_; | |
378 | |
379 | |
380 void Execute(const OrthancApiOracleCommand& command) | |
381 { | |
382 std::auto_ptr<Orthanc::HttpClient> client; | |
383 | |
384 { | |
385 NativeApplicationContext::ReaderLock lock(context_); | |
386 client.reset(new Orthanc::HttpClient(lock.GetOrthancParameters(), command.GetUri())); | |
387 } | |
388 | |
389 client->SetMethod(command.GetMethod()); | |
390 client->SetBody(command.GetBody()); | |
391 client->SetTimeout(command.GetTimeout()); | |
392 | |
393 { | |
394 const HttpHeaders& headers = command.GetHttpHeaders(); | |
395 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ ) | |
396 { | |
397 client->AddHeader(it->first, it->second); | |
398 } | |
399 } | |
400 | |
401 std::string answer; | |
402 HttpHeaders answerHeaders; | |
403 | |
404 bool success; | |
405 try | |
406 { | |
407 success = client->Apply(answer, answerHeaders); | |
408 } | |
409 catch (Orthanc::OrthancException& e) | |
410 { | |
411 success = false; | |
412 } | |
413 | |
414 { | |
415 NativeApplicationContext::WriterLock lock(context_); | |
416 | |
417 if (success) | |
418 { | |
419 OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer); | |
420 lock.GetOracleObservable().EmitMessage(message); | |
421 } | |
422 else | |
423 { | |
424 OrthancApiOracleCommand::FailureMessage message(command, client->GetLastStatus()); | |
425 lock.GetOracleObservable().EmitMessage(message); | |
426 } | |
427 } | |
428 } | |
429 | |
430 | |
431 | |
432 void Step() | |
433 { | |
434 std::auto_ptr<Orthanc::IDynamicObject> item(queue_.Dequeue(100)); | |
435 | |
436 if (item.get() != NULL) | |
437 { | |
438 const IOracleCommand& command = dynamic_cast<Item*>(item.get())->GetCommand(); | |
439 | |
440 switch (command.GetType()) | |
441 { | |
442 case IOracleCommand::Type_OrthancApi: | |
443 Execute(dynamic_cast<const OrthancApiOracleCommand&>(command)); | |
444 break; | |
445 | |
446 default: | |
447 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
448 } | |
449 } | |
450 } | |
451 | |
452 | |
453 static void Worker(NativeOracle* that) | |
454 { | |
455 assert(that != NULL); | |
456 | |
457 for (;;) | |
458 { | |
459 { | |
460 boost::mutex::scoped_lock lock(that->mutex_); | |
461 if (that->state_ != State_Running) | |
462 { | |
463 return; | |
464 } | |
465 } | |
466 | |
467 that->Step(); | |
468 } | |
469 } | |
470 | |
471 | |
472 void StopInternal() | |
473 { | |
474 { | |
475 boost::mutex::scoped_lock lock(mutex_); | |
476 | |
477 if (state_ == State_Setup || | |
478 state_ == State_Stopped) | |
479 { | |
480 return; | |
481 } | |
482 else | |
483 { | |
484 state_ = State_Stopped; | |
485 } | |
486 } | |
487 | |
488 for (size_t i = 0; i < workers_.size(); i++) | |
489 { | |
490 if (workers_[i] != NULL) | |
491 { | |
492 if (workers_[i]->joinable()) | |
493 { | |
494 workers_[i]->join(); | |
495 } | |
496 | |
497 delete workers_[i]; | |
498 } | |
499 } | |
500 } | |
501 | |
502 | |
503 public: | |
504 NativeOracle(NativeApplicationContext& context) : | |
505 context_(context), | |
506 state_(State_Setup), | |
507 workers_(4) | |
508 { | |
509 } | |
510 | |
511 virtual ~NativeOracle() | |
512 { | |
513 StopInternal(); | |
514 } | |
515 | |
516 void SetWorkersCount(unsigned int count) | |
517 { | |
518 boost::mutex::scoped_lock lock(mutex_); | |
519 | |
520 if (count <= 0) | |
521 { | |
522 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
523 } | |
524 else if (state_ != State_Setup) | |
525 { | |
526 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
527 } | |
528 else | |
529 { | |
530 workers_.resize(count); | |
531 } | |
532 } | |
533 | |
534 void Start() | |
535 { | |
536 boost::mutex::scoped_lock lock(mutex_); | |
537 | |
538 if (state_ != State_Setup) | |
539 { | |
540 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
541 } | |
542 else | |
543 { | |
544 state_ = State_Running; | |
545 | |
546 for (unsigned int i = 0; i < workers_.size(); i++) | |
547 { | |
548 workers_[i] = new boost::thread(Worker, this); | |
549 } | |
550 } | |
551 } | |
552 | |
553 void Stop() | |
554 { | |
555 StopInternal(); | |
556 } | |
557 | |
558 virtual void Schedule(IOracleCommand* command) | |
559 { | |
560 queue_.Enqueue(new Item(command)); | |
561 } | |
562 }; | |
563 } | |
564 | |
565 | |
566 | |
567 class Toto : public OrthancStone::IObserver | |
568 { | |
569 private: | |
570 void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message) | |
571 { | |
572 Json::Value v; | |
573 message.GetJsonBody(v); | |
574 | |
575 printf("ICI [%s]\n", v.toStyledString().c_str()); | |
576 } | |
577 | |
578 void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message) | |
579 { | |
580 printf("ERROR %d\n", message.GetHttpStatus()); | |
581 } | |
582 | |
583 public: | |
584 Toto(OrthancStone::IObservable& oracle) : | |
585 IObserver(oracle.GetBroker()) | |
586 { | |
587 oracle.RegisterObserverCallback(new OrthancStone::Callable<Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle)); | |
588 oracle.RegisterObserverCallback(new OrthancStone::Callable<Toto, Refactoring::OrthancApiOracleCommand::FailureMessage>(*this, &Toto::Handle)); | |
589 } | |
590 }; | |
591 | |
592 | |
593 void Run(Refactoring::NativeApplicationContext& context) | |
594 { | |
595 std::auto_ptr<Toto> toto; | |
596 | |
597 { | |
598 Refactoring::NativeApplicationContext::WriterLock lock(context); | |
599 toto.reset(new Toto(lock.GetOracleObservable())); | |
600 } | |
601 | |
602 Refactoring::NativeOracle oracle(context); | |
603 oracle.Start(); | |
604 | |
605 { | |
606 Json::Value v = Json::objectValue; | |
607 v["Level"] = "Series"; | |
608 v["Query"] = Json::objectValue; | |
609 | |
610 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand); | |
611 command->SetMethod(Orthanc::HttpMethod_Post); | |
612 command->SetUri("/tools/find"); | |
613 command->SetBody(v); | |
614 | |
615 oracle.Schedule(command.release()); | |
616 } | |
617 | |
618 boost::this_thread::sleep(boost::posix_time::seconds(1)); | |
619 | |
620 oracle.Stop(); | |
621 } | |
622 | |
623 | |
624 | |
625 /** | |
626 * IMPORTANT: The full arguments to "main()" are needed for SDL on | |
627 * Windows. Otherwise, one gets the linking error "undefined reference | |
628 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows | |
629 **/ | |
630 int main(int argc, char* argv[]) | |
631 { | |
632 OrthancStone::StoneInitialize(); | |
633 Orthanc::Logging::EnableInfoLevel(true); | |
634 | |
635 try | |
636 { | |
637 Refactoring::NativeApplicationContext context; | |
638 Run(context); | |
639 } | |
640 catch (Orthanc::OrthancException& e) | |
641 { | |
642 LOG(ERROR) << "EXCEPTION: " << e.What(); | |
643 } | |
644 | |
645 OrthancStone::StoneFinalize(); | |
646 | |
647 return 0; | |
648 } |