comparison OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.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 OrthancServer/OrthancRestApi/OrthancRestSystem.cpp@e3b3af80732d
children 05b8fd21089c
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 "../PrecompiledHeadersServer.h"
35 #include "OrthancRestApi.h"
36
37 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
38 #include "../../Core/MetricsRegistry.h"
39 #include "../../Plugins/Engine/OrthancPlugins.h"
40 #include "../../Plugins/Engine/PluginsManager.h"
41 #include "../OrthancConfiguration.h"
42 #include "../ServerContext.h"
43
44
45 static const char* LOG_LEVEL_DEFAULT = "default";
46 static const char* LOG_LEVEL_VERBOSE = "verbose";
47 static const char* LOG_LEVEL_TRACE = "trace";
48
49
50 namespace Orthanc
51 {
52 // System information -------------------------------------------------------
53
54 static void ServeRoot(RestApiGetCall& call)
55 {
56 call.GetOutput().Redirect("app/explorer.html");
57 }
58
59 static void GetSystemInformation(RestApiGetCall& call)
60 {
61 ServerContext& context = OrthancRestApi::GetContext(call);
62
63 Json::Value result = Json::objectValue;
64
65 result["ApiVersion"] = ORTHANC_API_VERSION;
66 result["Version"] = ORTHANC_VERSION;
67 result["DatabaseVersion"] = OrthancRestApi::GetIndex(call).GetDatabaseVersion();
68 result["IsHttpServerSecure"] = context.IsHttpServerSecure(); // New in Orthanc 1.5.8
69
70 {
71 OrthancConfiguration::ReaderLock lock;
72 result["DicomAet"] = lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC");
73 result["DicomPort"] = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomPort", 4242);
74 result["HttpPort"] = lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042);
75 result["Name"] = lock.GetConfiguration().GetStringParameter("Name", "");
76 }
77
78 result["StorageAreaPlugin"] = Json::nullValue;
79 result["DatabaseBackendPlugin"] = Json::nullValue;
80
81 #if ORTHANC_ENABLE_PLUGINS == 1
82 result["PluginsEnabled"] = true;
83 const OrthancPlugins& plugins = context.GetPlugins();
84
85 if (plugins.HasStorageArea())
86 {
87 std::string p = plugins.GetStorageAreaLibrary().GetPath();
88 result["StorageAreaPlugin"] = boost::filesystem::canonical(p).string();
89 }
90
91 if (plugins.HasDatabaseBackend())
92 {
93 std::string p = plugins.GetDatabaseBackendLibrary().GetPath();
94 result["DatabaseBackendPlugin"] = boost::filesystem::canonical(p).string();
95 }
96 #else
97 result["PluginsEnabled"] = false;
98 #endif
99
100 call.GetOutput().AnswerJson(result);
101 }
102
103 static void GetStatistics(RestApiGetCall& call)
104 {
105 static const uint64_t MEGA_BYTES = 1024 * 1024;
106
107 uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances;
108 OrthancRestApi::GetIndex(call).GetGlobalStatistics(diskSize, uncompressedSize, countPatients,
109 countStudies, countSeries, countInstances);
110
111 Json::Value result = Json::objectValue;
112 result["TotalDiskSize"] = boost::lexical_cast<std::string>(diskSize);
113 result["TotalUncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize);
114 result["TotalDiskSizeMB"] = static_cast<unsigned int>(diskSize / MEGA_BYTES);
115 result["TotalUncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
116 result["CountPatients"] = static_cast<unsigned int>(countPatients);
117 result["CountStudies"] = static_cast<unsigned int>(countStudies);
118 result["CountSeries"] = static_cast<unsigned int>(countSeries);
119 result["CountInstances"] = static_cast<unsigned int>(countInstances);
120
121 call.GetOutput().AnswerJson(result);
122 }
123
124 static void GenerateUid(RestApiGetCall& call)
125 {
126 std::string level = call.GetArgument("level", "");
127 if (level == "patient")
128 {
129 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient), MimeType_PlainText);
130 }
131 else if (level == "study")
132 {
133 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study), MimeType_PlainText);
134 }
135 else if (level == "series")
136 {
137 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series), MimeType_PlainText);
138 }
139 else if (level == "instance")
140 {
141 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance), MimeType_PlainText);
142 }
143 }
144
145 static void ExecuteScript(RestApiPostCall& call)
146 {
147 ServerContext& context = OrthancRestApi::GetContext(call);
148
149 if (!context.IsExecuteLuaEnabled())
150 {
151 LOG(ERROR) << "The URI /tools/execute-script is disallowed for security, "
152 << "check your configuration file";
153 call.GetOutput().SignalError(HttpStatus_403_Forbidden);
154 return;
155 }
156
157 std::string result;
158 std::string command;
159 call.BodyToString(command);
160
161 {
162 LuaScripting::Lock lock(context.GetLuaScripting());
163 lock.GetLua().Execute(result, command);
164 }
165
166 call.GetOutput().AnswerBuffer(result, MimeType_PlainText);
167 }
168
169 template <bool UTC>
170 static void GetNowIsoString(RestApiGetCall& call)
171 {
172 call.GetOutput().AnswerBuffer(SystemToolbox::GetNowIsoString(UTC), MimeType_PlainText);
173 }
174
175
176 static void GetDicomConformanceStatement(RestApiGetCall& call)
177 {
178 std::string statement;
179 GetFileResource(statement, ServerResources::DICOM_CONFORMANCE_STATEMENT);
180 call.GetOutput().AnswerBuffer(statement, MimeType_PlainText);
181 }
182
183
184 static void GetDefaultEncoding(RestApiGetCall& call)
185 {
186 Encoding encoding = GetDefaultDicomEncoding();
187 call.GetOutput().AnswerBuffer(EnumerationToString(encoding), MimeType_PlainText);
188 }
189
190
191 static void SetDefaultEncoding(RestApiPutCall& call)
192 {
193 std::string body;
194 call.BodyToString(body);
195
196 Encoding encoding = StringToEncoding(body.c_str());
197
198 {
199 OrthancConfiguration::WriterLock lock;
200 lock.GetConfiguration().SetDefaultEncoding(encoding);
201 }
202
203 call.GetOutput().AnswerBuffer(EnumerationToString(encoding), MimeType_PlainText);
204 }
205
206
207
208 // Plugins information ------------------------------------------------------
209
210 static void ListPlugins(RestApiGetCall& call)
211 {
212 Json::Value v = Json::arrayValue;
213
214 v.append("explorer.js");
215
216 if (OrthancRestApi::GetContext(call).HasPlugins())
217 {
218 #if ORTHANC_ENABLE_PLUGINS == 1
219 std::list<std::string> plugins;
220 OrthancRestApi::GetContext(call).GetPlugins().GetManager().ListPlugins(plugins);
221
222 for (std::list<std::string>::const_iterator
223 it = plugins.begin(); it != plugins.end(); ++it)
224 {
225 v.append(*it);
226 }
227 #endif
228 }
229
230 call.GetOutput().AnswerJson(v);
231 }
232
233
234 static void GetPlugin(RestApiGetCall& call)
235 {
236 if (!OrthancRestApi::GetContext(call).HasPlugins())
237 {
238 return;
239 }
240
241 #if ORTHANC_ENABLE_PLUGINS == 1
242 const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPlugins().GetManager();
243 std::string id = call.GetUriComponent("id", "");
244
245 if (manager.HasPlugin(id))
246 {
247 Json::Value v = Json::objectValue;
248 v["ID"] = id;
249 v["Version"] = manager.GetPluginVersion(id);
250
251 const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins();
252 const char *c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_RootUri);
253 if (c != NULL)
254 {
255 std::string root = c;
256 if (!root.empty())
257 {
258 // Turn the root URI into a URI relative to "/app/explorer.js"
259 if (root[0] == '/')
260 {
261 root = ".." + root;
262 }
263
264 v["RootUri"] = root;
265 }
266 }
267
268 c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_Description);
269 if (c != NULL)
270 {
271 v["Description"] = c;
272 }
273
274 c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_OrthancExplorer);
275 v["ExtendsOrthancExplorer"] = (c != NULL);
276
277 call.GetOutput().AnswerJson(v);
278 }
279 #endif
280 }
281
282
283 static void GetOrthancExplorerPlugins(RestApiGetCall& call)
284 {
285 std::string s = "// Extensions to Orthanc Explorer by the registered plugins\n\n";
286
287 if (OrthancRestApi::GetContext(call).HasPlugins())
288 {
289 #if ORTHANC_ENABLE_PLUGINS == 1
290 const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins();
291 const PluginsManager& manager = plugins.GetManager();
292
293 std::list<std::string> lst;
294 manager.ListPlugins(lst);
295
296 for (std::list<std::string>::const_iterator
297 it = lst.begin(); it != lst.end(); ++it)
298 {
299 const char* tmp = plugins.GetProperty(it->c_str(), _OrthancPluginProperty_OrthancExplorer);
300 if (tmp != NULL)
301 {
302 s += "/**\n * From plugin: " + *it + " (version " + manager.GetPluginVersion(*it) + ")\n **/\n\n";
303 s += std::string(tmp) + "\n\n";
304 }
305 }
306 #endif
307 }
308
309 call.GetOutput().AnswerBuffer(s, MimeType_JavaScript);
310 }
311
312
313
314
315 // Jobs information ------------------------------------------------------
316
317 static void ListJobs(RestApiGetCall& call)
318 {
319 bool expand = call.HasArgument("expand");
320
321 Json::Value v = Json::arrayValue;
322
323 std::set<std::string> jobs;
324 OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().ListJobs(jobs);
325
326 for (std::set<std::string>::const_iterator it = jobs.begin();
327 it != jobs.end(); ++it)
328 {
329 if (expand)
330 {
331 JobInfo info;
332 if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, *it))
333 {
334 Json::Value tmp;
335 info.Format(tmp);
336 v.append(tmp);
337 }
338 }
339 else
340 {
341 v.append(*it);
342 }
343 }
344
345 call.GetOutput().AnswerJson(v);
346 }
347
348 static void GetJobInfo(RestApiGetCall& call)
349 {
350 std::string id = call.GetUriComponent("id", "");
351
352 JobInfo info;
353 if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, id))
354 {
355 Json::Value json;
356 info.Format(json);
357 call.GetOutput().AnswerJson(json);
358 }
359 }
360
361
362 static void GetJobOutput(RestApiGetCall& call)
363 {
364 std::string job = call.GetUriComponent("id", "");
365 std::string key = call.GetUriComponent("key", "");
366
367 std::string value;
368 MimeType mime;
369
370 if (OrthancRestApi::GetContext(call).GetJobsEngine().
371 GetRegistry().GetJobOutput(value, mime, job, key))
372 {
373 call.GetOutput().AnswerBuffer(value, mime);
374 }
375 else
376 {
377 throw OrthancException(ErrorCode_InexistentItem,
378 "Job has no such output: " + key);
379 }
380 }
381
382
383 enum JobAction
384 {
385 JobAction_Cancel,
386 JobAction_Pause,
387 JobAction_Resubmit,
388 JobAction_Resume
389 };
390
391 template <JobAction action>
392 static void ApplyJobAction(RestApiPostCall& call)
393 {
394 std::string id = call.GetUriComponent("id", "");
395
396 bool ok = false;
397
398 switch (action)
399 {
400 case JobAction_Cancel:
401 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Cancel(id);
402 break;
403
404 case JobAction_Pause:
405 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Pause(id);
406 break;
407
408 case JobAction_Resubmit:
409 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resubmit(id);
410 break;
411
412 case JobAction_Resume:
413 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resume(id);
414 break;
415
416 default:
417 throw OrthancException(ErrorCode_InternalError);
418 }
419
420 if (ok)
421 {
422 call.GetOutput().AnswerBuffer("{}", MimeType_Json);
423 }
424 }
425
426
427 static void GetMetricsPrometheus(RestApiGetCall& call)
428 {
429 #if ORTHANC_ENABLE_PLUGINS == 1
430 OrthancRestApi::GetContext(call).GetPlugins().RefreshMetrics();
431 #endif
432
433 static const float MEGA_BYTES = 1024 * 1024;
434
435 ServerContext& context = OrthancRestApi::GetContext(call);
436
437 uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances;
438 context.GetIndex().GetGlobalStatistics(diskSize, uncompressedSize, countPatients,
439 countStudies, countSeries, countInstances);
440
441 unsigned int jobsPending, jobsRunning, jobsSuccess, jobsFailed;
442 context.GetJobsEngine().GetRegistry().GetStatistics(jobsPending, jobsRunning, jobsSuccess, jobsFailed);
443
444 MetricsRegistry& registry = context.GetMetricsRegistry();
445 registry.SetValue("orthanc_disk_size_mb", static_cast<float>(diskSize) / MEGA_BYTES);
446 registry.SetValue("orthanc_uncompressed_size_mb", static_cast<float>(diskSize) / MEGA_BYTES);
447 registry.SetValue("orthanc_count_patients", static_cast<unsigned int>(countPatients));
448 registry.SetValue("orthanc_count_studies", static_cast<unsigned int>(countStudies));
449 registry.SetValue("orthanc_count_series", static_cast<unsigned int>(countSeries));
450 registry.SetValue("orthanc_count_instances", static_cast<unsigned int>(countInstances));
451 registry.SetValue("orthanc_jobs_pending", jobsPending);
452 registry.SetValue("orthanc_jobs_running", jobsRunning);
453 registry.SetValue("orthanc_jobs_completed", jobsSuccess + jobsFailed);
454 registry.SetValue("orthanc_jobs_success", jobsSuccess);
455 registry.SetValue("orthanc_jobs_failed", jobsFailed);
456
457 std::string s;
458 registry.ExportPrometheusText(s);
459
460 call.GetOutput().AnswerBuffer(s, MimeType_PrometheusText);
461 }
462
463
464 static void GetMetricsEnabled(RestApiGetCall& call)
465 {
466 bool enabled = OrthancRestApi::GetContext(call).GetMetricsRegistry().IsEnabled();
467 call.GetOutput().AnswerBuffer(enabled ? "1" : "0", MimeType_PlainText);
468 }
469
470
471 static void PutMetricsEnabled(RestApiPutCall& call)
472 {
473 bool enabled;
474
475 std::string body;
476 call.BodyToString(body);
477
478 if (body == "1")
479 {
480 enabled = true;
481 }
482 else if (body == "0")
483 {
484 enabled = false;
485 }
486 else
487 {
488 throw OrthancException(ErrorCode_ParameterOutOfRange,
489 "The HTTP body must be 0 or 1, but found: " + body);
490 }
491
492 // Success
493 OrthancRestApi::GetContext(call).GetMetricsRegistry().SetEnabled(enabled);
494 call.GetOutput().AnswerBuffer("", MimeType_PlainText);
495 }
496
497
498 static void GetLogLevel(RestApiGetCall& call)
499 {
500 std::string s;
501
502 if (Logging::IsTraceLevelEnabled())
503 {
504 s = LOG_LEVEL_TRACE;
505 }
506 else if (Logging::IsInfoLevelEnabled())
507 {
508 s = LOG_LEVEL_VERBOSE;
509 }
510 else
511 {
512 s = LOG_LEVEL_DEFAULT;
513 }
514
515 call.GetOutput().AnswerBuffer(s, MimeType_PlainText);
516 }
517
518
519 static void PutLogLevel(RestApiPutCall& call)
520 {
521 std::string body;
522 call.BodyToString(body);
523
524 if (body == LOG_LEVEL_DEFAULT)
525 {
526 Logging::EnableInfoLevel(false);
527 Logging::EnableTraceLevel(false);
528 }
529 else if (body == LOG_LEVEL_VERBOSE)
530 {
531 Logging::EnableInfoLevel(true);
532 Logging::EnableTraceLevel(false);
533 }
534 else if (body == LOG_LEVEL_TRACE)
535 {
536 Logging::EnableInfoLevel(true);
537 Logging::EnableTraceLevel(true);
538 }
539 else
540 {
541 throw OrthancException(ErrorCode_ParameterOutOfRange,
542 "The log level must be one of the following values: \"" +
543 std::string(LOG_LEVEL_DEFAULT) + "\", \"" +
544 std::string(LOG_LEVEL_VERBOSE) + "\", of \"" +
545 std::string(LOG_LEVEL_TRACE) + "\"");
546 }
547
548 // Success
549 LOG(WARNING) << "REST API call has switched the log level to: " << body;
550 call.GetOutput().AnswerBuffer("", MimeType_PlainText);
551 }
552
553
554 void OrthancRestApi::RegisterSystem()
555 {
556 Register("/", ServeRoot);
557 Register("/system", GetSystemInformation);
558 Register("/statistics", GetStatistics);
559 Register("/tools/generate-uid", GenerateUid);
560 Register("/tools/execute-script", ExecuteScript);
561 Register("/tools/now", GetNowIsoString<true>);
562 Register("/tools/now-local", GetNowIsoString<false>);
563 Register("/tools/dicom-conformance", GetDicomConformanceStatement);
564 Register("/tools/default-encoding", GetDefaultEncoding);
565 Register("/tools/default-encoding", SetDefaultEncoding);
566 Register("/tools/metrics", GetMetricsEnabled);
567 Register("/tools/metrics", PutMetricsEnabled);
568 Register("/tools/metrics-prometheus", GetMetricsPrometheus);
569 Register("/tools/log-level", GetLogLevel);
570 Register("/tools/log-level", PutLogLevel);
571
572 Register("/plugins", ListPlugins);
573 Register("/plugins/{id}", GetPlugin);
574 Register("/plugins/explorer.js", GetOrthancExplorerPlugins);
575
576 Register("/jobs", ListJobs);
577 Register("/jobs/{id}", GetJobInfo);
578 Register("/jobs/{id}/cancel", ApplyJobAction<JobAction_Cancel>);
579 Register("/jobs/{id}/pause", ApplyJobAction<JobAction_Pause>);
580 Register("/jobs/{id}/resubmit", ApplyJobAction<JobAction_Resubmit>);
581 Register("/jobs/{id}/resume", ApplyJobAction<JobAction_Resume>);
582 Register("/jobs/{id}/{key}", GetJobOutput);
583 }
584 }