comparison OrthancFramework/Sources/DicomNetworking/DicomServer.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/DicomNetworking/DicomServer.cpp@7f296ae25039
children bf7b9edf6b81
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 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 General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "../PrecompiledHeaders.h"
35 #include "DicomServer.h"
36
37 #include "../Logging.h"
38 #include "../MultiThreading/RunnableWorkersPool.h"
39 #include "../OrthancException.h"
40 #include "../Toolbox.h"
41 #include "Internals/CommandDispatcher.h"
42
43 #include <boost/thread.hpp>
44
45 #if defined(__linux__)
46 #include <cstdlib>
47 #endif
48
49
50 namespace Orthanc
51 {
52 struct DicomServer::PImpl
53 {
54 boost::thread thread_;
55 T_ASC_Network *network_;
56 std::unique_ptr<RunnableWorkersPool> workers_;
57 };
58
59
60 void DicomServer::ServerThread(DicomServer* server)
61 {
62 LOG(INFO) << "DICOM server started";
63
64 while (server->continue_)
65 {
66 /* receive an association and acknowledge or reject it. If the association was */
67 /* acknowledged, offer corresponding services and invoke one or more if required. */
68 std::unique_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_));
69
70 try
71 {
72 if (dispatcher.get() != NULL)
73 {
74 server->pimpl_->workers_->Add(dispatcher.release());
75 }
76 }
77 catch (OrthancException& e)
78 {
79 LOG(ERROR) << "Exception in the DICOM server thread: " << e.What();
80 }
81 }
82
83 LOG(INFO) << "DICOM server stopping";
84 }
85
86
87 DicomServer::DicomServer() :
88 pimpl_(new PImpl),
89 aet_("ANY-SCP")
90 {
91 port_ = 104;
92 modalities_ = NULL;
93 findRequestHandlerFactory_ = NULL;
94 moveRequestHandlerFactory_ = NULL;
95 getRequestHandlerFactory_ = NULL;
96 storeRequestHandlerFactory_ = NULL;
97 worklistRequestHandlerFactory_ = NULL;
98 storageCommitmentFactory_ = NULL;
99 applicationEntityFilter_ = NULL;
100 checkCalledAet_ = true;
101 associationTimeout_ = 30;
102 continue_ = false;
103 }
104
105 DicomServer::~DicomServer()
106 {
107 if (continue_)
108 {
109 LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!";
110 Stop();
111 }
112 }
113
114 void DicomServer::SetPortNumber(uint16_t port)
115 {
116 Stop();
117 port_ = port;
118 }
119
120 uint16_t DicomServer::GetPortNumber() const
121 {
122 return port_;
123 }
124
125 void DicomServer::SetAssociationTimeout(uint32_t seconds)
126 {
127 LOG(INFO) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): "
128 << seconds << " seconds (0 = no timeout)";
129
130 Stop();
131 associationTimeout_ = seconds;
132 }
133
134 uint32_t DicomServer::GetAssociationTimeout() const
135 {
136 return associationTimeout_;
137 }
138
139
140 void DicomServer::SetCalledApplicationEntityTitleCheck(bool check)
141 {
142 Stop();
143 checkCalledAet_ = check;
144 }
145
146 bool DicomServer::HasCalledApplicationEntityTitleCheck() const
147 {
148 return checkCalledAet_;
149 }
150
151 void DicomServer::SetApplicationEntityTitle(const std::string& aet)
152 {
153 if (aet.size() == 0)
154 {
155 throw OrthancException(ErrorCode_BadApplicationEntityTitle);
156 }
157
158 if (aet.size() > 16)
159 {
160 throw OrthancException(ErrorCode_BadApplicationEntityTitle);
161 }
162
163 for (size_t i = 0; i < aet.size(); i++)
164 {
165 if (!(aet[i] == '-' ||
166 aet[i] == '_' ||
167 isdigit(aet[i]) ||
168 (aet[i] >= 'A' && aet[i] <= 'Z')))
169 {
170 LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\"";
171 break;
172 }
173 }
174
175 Stop();
176 aet_ = aet;
177 }
178
179 const std::string& DicomServer::GetApplicationEntityTitle() const
180 {
181 return aet_;
182 }
183
184 void DicomServer::SetRemoteModalities(IRemoteModalities& modalities)
185 {
186 Stop();
187 modalities_ = &modalities;
188 }
189
190 DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const
191 {
192 if (modalities_ == NULL)
193 {
194 throw OrthancException(ErrorCode_BadSequenceOfCalls);
195 }
196 else
197 {
198 return *modalities_;
199 }
200 }
201
202 void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory)
203 {
204 Stop();
205 findRequestHandlerFactory_ = &factory;
206 }
207
208 bool DicomServer::HasFindRequestHandlerFactory() const
209 {
210 return (findRequestHandlerFactory_ != NULL);
211 }
212
213 IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const
214 {
215 if (HasFindRequestHandlerFactory())
216 {
217 return *findRequestHandlerFactory_;
218 }
219 else
220 {
221 throw OrthancException(ErrorCode_NoCFindHandler);
222 }
223 }
224
225 void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory)
226 {
227 Stop();
228 moveRequestHandlerFactory_ = &factory;
229 }
230
231 bool DicomServer::HasMoveRequestHandlerFactory() const
232 {
233 return (moveRequestHandlerFactory_ != NULL);
234 }
235
236 IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const
237 {
238 if (HasMoveRequestHandlerFactory())
239 {
240 return *moveRequestHandlerFactory_;
241 }
242 else
243 {
244 throw OrthancException(ErrorCode_NoCMoveHandler);
245 }
246 }
247
248 void DicomServer::SetGetRequestHandlerFactory(IGetRequestHandlerFactory& factory)
249 {
250 Stop();
251 getRequestHandlerFactory_ = &factory;
252 }
253
254 bool DicomServer::HasGetRequestHandlerFactory() const
255 {
256 return (getRequestHandlerFactory_ != NULL);
257 }
258
259 IGetRequestHandlerFactory& DicomServer::GetGetRequestHandlerFactory() const
260 {
261 if (HasGetRequestHandlerFactory())
262 {
263 return *getRequestHandlerFactory_;
264 }
265 else
266 {
267 throw OrthancException(ErrorCode_NoCGetHandler);
268 }
269 }
270
271 void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory)
272 {
273 Stop();
274 storeRequestHandlerFactory_ = &factory;
275 }
276
277 bool DicomServer::HasStoreRequestHandlerFactory() const
278 {
279 return (storeRequestHandlerFactory_ != NULL);
280 }
281
282 IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const
283 {
284 if (HasStoreRequestHandlerFactory())
285 {
286 return *storeRequestHandlerFactory_;
287 }
288 else
289 {
290 throw OrthancException(ErrorCode_NoCStoreHandler);
291 }
292 }
293
294 void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory)
295 {
296 Stop();
297 worklistRequestHandlerFactory_ = &factory;
298 }
299
300 bool DicomServer::HasWorklistRequestHandlerFactory() const
301 {
302 return (worklistRequestHandlerFactory_ != NULL);
303 }
304
305 IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const
306 {
307 if (HasWorklistRequestHandlerFactory())
308 {
309 return *worklistRequestHandlerFactory_;
310 }
311 else
312 {
313 throw OrthancException(ErrorCode_NoWorklistHandler);
314 }
315 }
316
317 void DicomServer::SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& factory)
318 {
319 Stop();
320 storageCommitmentFactory_ = &factory;
321 }
322
323 bool DicomServer::HasStorageCommitmentRequestHandlerFactory() const
324 {
325 return (storageCommitmentFactory_ != NULL);
326 }
327
328 IStorageCommitmentRequestHandlerFactory& DicomServer::GetStorageCommitmentRequestHandlerFactory() const
329 {
330 if (HasStorageCommitmentRequestHandlerFactory())
331 {
332 return *storageCommitmentFactory_;
333 }
334 else
335 {
336 throw OrthancException(ErrorCode_NoStorageCommitmentHandler);
337 }
338 }
339
340 void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory)
341 {
342 Stop();
343 applicationEntityFilter_ = &factory;
344 }
345
346 bool DicomServer::HasApplicationEntityFilter() const
347 {
348 return (applicationEntityFilter_ != NULL);
349 }
350
351 IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const
352 {
353 if (HasApplicationEntityFilter())
354 {
355 return *applicationEntityFilter_;
356 }
357 else
358 {
359 throw OrthancException(ErrorCode_NoApplicationEntityFilter);
360 }
361 }
362
363 void DicomServer::Start()
364 {
365 if (modalities_ == NULL)
366 {
367 throw OrthancException(ErrorCode_BadSequenceOfCalls,
368 "No list of modalities was provided to the DICOM server");
369 }
370
371 Stop();
372
373 /* initialize network, i.e. create an instance of T_ASC_Network*. */
374 OFCondition cond = ASC_initializeNetwork
375 (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_);
376 if (cond.bad())
377 {
378 throw OrthancException(ErrorCode_DicomPortInUse,
379 " (port = " + boost::lexical_cast<std::string>(port_) + ") cannot create network: " + std::string(cond.text()));
380 }
381
382 continue_ = true;
383 pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter?
384 pimpl_->thread_ = boost::thread(ServerThread, this);
385 }
386
387
388 void DicomServer::Stop()
389 {
390 if (continue_)
391 {
392 continue_ = false;
393
394 if (pimpl_->thread_.joinable())
395 {
396 pimpl_->thread_.join();
397 }
398
399 pimpl_->workers_.reset(NULL);
400
401 /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
402 /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
403 OFCondition cond = ASC_dropNetwork(&pimpl_->network_);
404 if (cond.bad())
405 {
406 LOG(ERROR) << "Error while dropping the network: " << cond.text();
407 }
408 }
409 }
410
411
412 bool DicomServer::IsMyAETitle(const std::string& aet) const
413 {
414 if (modalities_ == NULL)
415 {
416 throw OrthancException(ErrorCode_BadSequenceOfCalls);
417 }
418
419 if (!HasCalledApplicationEntityTitleCheck())
420 {
421 // OK, no check on the AET.
422 return true;
423 }
424 else
425 {
426 return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle());
427 }
428 }
429 }