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 }