comparison OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.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/OrthancRestArchive.cpp@6ddad3e0b569
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/HttpServer/FilesystemHttpSender.h"
38 #include "../../Core/OrthancException.h"
39 #include "../../Core/SerializationToolbox.h"
40 #include "../OrthancConfiguration.h"
41 #include "../ServerContext.h"
42 #include "../ServerJobs/ArchiveJob.h"
43
44
45 namespace Orthanc
46 {
47 static const char* const KEY_RESOURCES = "Resources";
48 static const char* const KEY_EXTENDED = "Extended";
49 static const char* const KEY_TRANSCODE = "Transcode";
50
51 static void AddResourcesOfInterestFromArray(ArchiveJob& job,
52 const Json::Value& resources)
53 {
54 if (resources.type() != Json::arrayValue)
55 {
56 throw OrthancException(ErrorCode_BadFileFormat,
57 "Expected a list of strings (Orthanc identifiers)");
58 }
59
60 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
61 {
62 if (resources[i].type() != Json::stringValue)
63 {
64 throw OrthancException(ErrorCode_BadFileFormat,
65 "Expected a list of strings (Orthanc identifiers)");
66 }
67 else
68 {
69 job.AddResource(resources[i].asString());
70 }
71 }
72 }
73
74
75 static void AddResourcesOfInterest(ArchiveJob& job /* inout */,
76 const Json::Value& body /* in */)
77 {
78 if (body.type() == Json::arrayValue)
79 {
80 AddResourcesOfInterestFromArray(job, body);
81 }
82 else if (body.type() == Json::objectValue)
83 {
84 if (body.isMember(KEY_RESOURCES))
85 {
86 AddResourcesOfInterestFromArray(job, body[KEY_RESOURCES]);
87 }
88 else
89 {
90 throw OrthancException(ErrorCode_BadFileFormat,
91 "Missing field " + std::string(KEY_RESOURCES) +
92 " in the JSON body");
93 }
94 }
95 else
96 {
97 throw OrthancException(ErrorCode_BadFileFormat);
98 }
99 }
100
101
102 static DicomTransferSyntax GetTransferSyntax(const std::string& value)
103 {
104 DicomTransferSyntax syntax;
105 if (LookupTransferSyntax(syntax, value))
106 {
107 return syntax;
108 }
109 else
110 {
111 throw OrthancException(ErrorCode_ParameterOutOfRange,
112 "Unknown transfer syntax: " + value);
113 }
114 }
115
116
117 static void GetJobParameters(bool& synchronous, /* out */
118 bool& extended, /* out */
119 bool& transcode, /* out */
120 DicomTransferSyntax& syntax, /* out */
121 int& priority, /* out */
122 const Json::Value& body, /* in */
123 const bool defaultExtended /* in */)
124 {
125 synchronous = OrthancRestApi::IsSynchronousJobRequest
126 (true /* synchronous by default */, body);
127
128 priority = OrthancRestApi::GetJobRequestPriority(body);
129
130 if (body.type() == Json::objectValue &&
131 body.isMember(KEY_EXTENDED))
132 {
133 extended = SerializationToolbox::ReadBoolean(body, KEY_EXTENDED);
134 }
135 else
136 {
137 extended = defaultExtended;
138 }
139
140 if (body.type() == Json::objectValue &&
141 body.isMember(KEY_TRANSCODE))
142 {
143 transcode = true;
144 syntax = GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE));
145 }
146 else
147 {
148 transcode = false;
149 }
150 }
151
152
153 static void SubmitJob(RestApiOutput& output,
154 ServerContext& context,
155 std::unique_ptr<ArchiveJob>& job,
156 int priority,
157 bool synchronous,
158 const std::string& filename)
159 {
160 if (job.get() == NULL)
161 {
162 throw OrthancException(ErrorCode_NullPointer);
163 }
164
165 job->SetDescription("REST API");
166
167 if (synchronous)
168 {
169 boost::shared_ptr<TemporaryFile> tmp;
170
171 {
172 OrthancConfiguration::ReaderLock lock;
173 tmp.reset(lock.GetConfiguration().CreateTemporaryFile());
174 }
175
176 job->SetSynchronousTarget(tmp);
177
178 Json::Value publicContent;
179 context.GetJobsEngine().GetRegistry().SubmitAndWait
180 (publicContent, job.release(), priority);
181
182 {
183 // The archive is now created: Prepare the sending of the ZIP file
184 FilesystemHttpSender sender(tmp->GetPath(), MimeType_Zip);
185 sender.SetContentFilename(filename);
186
187 // Send the ZIP
188 output.AnswerStream(sender);
189 }
190 }
191 else
192 {
193 OrthancRestApi::SubmitGenericJob(output, context, job.release(), false, priority);
194 }
195 }
196
197
198 template <bool IS_MEDIA,
199 bool DEFAULT_IS_EXTENDED /* only makes sense for media (i.e. not ZIP archives) */ >
200 static void CreateBatch(RestApiPostCall& call)
201 {
202 ServerContext& context = OrthancRestApi::GetContext(call);
203
204 Json::Value body;
205 if (call.ParseJsonRequest(body))
206 {
207 bool synchronous, extended, transcode;
208 DicomTransferSyntax transferSyntax;
209 int priority;
210 GetJobParameters(synchronous, extended, transcode, transferSyntax,
211 priority, body, DEFAULT_IS_EXTENDED);
212
213 std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
214 AddResourcesOfInterest(*job, body);
215
216 if (transcode)
217 {
218 job->SetTranscode(transferSyntax);
219 }
220
221 SubmitJob(call.GetOutput(), context, job, priority, synchronous, "Archive.zip");
222 }
223 else
224 {
225 throw OrthancException(ErrorCode_BadFileFormat,
226 "Expected a list of resources to archive in the body");
227 }
228 }
229
230
231 template <bool IS_MEDIA,
232 bool DEFAULT_IS_EXTENDED /* only makes sense for media (i.e. not ZIP archives) */ >
233 static void CreateSingleGet(RestApiGetCall& call)
234 {
235 ServerContext& context = OrthancRestApi::GetContext(call);
236
237 std::string id = call.GetUriComponent("id", "");
238
239 bool extended;
240 if (IS_MEDIA)
241 {
242 extended = call.HasArgument("extended");
243 }
244 else
245 {
246 extended = false;
247 }
248
249 std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
250 job->AddResource(id);
251
252 static const char* const TRANSCODE = "transcode";
253 if (call.HasArgument(TRANSCODE))
254 {
255 job->SetTranscode(GetTransferSyntax(call.GetArgument(TRANSCODE, "")));
256 }
257
258 SubmitJob(call.GetOutput(), context, job, 0 /* priority */,
259 true /* synchronous */, id + ".zip");
260 }
261
262
263 template <bool IS_MEDIA,
264 bool DEFAULT_IS_EXTENDED /* only makes sense for media (i.e. not ZIP archives) */ >
265 static void CreateSinglePost(RestApiPostCall& call)
266 {
267 ServerContext& context = OrthancRestApi::GetContext(call);
268
269 std::string id = call.GetUriComponent("id", "");
270
271 Json::Value body;
272 if (call.ParseJsonRequest(body))
273 {
274 bool synchronous, extended, transcode;
275 DicomTransferSyntax transferSyntax;
276 int priority;
277 GetJobParameters(synchronous, extended, transcode, transferSyntax,
278 priority, body, DEFAULT_IS_EXTENDED);
279
280 std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
281 job->AddResource(id);
282
283 if (transcode)
284 {
285 job->SetTranscode(transferSyntax);
286 }
287
288 SubmitJob(call.GetOutput(), context, job, priority, synchronous, id + ".zip");
289 }
290 else
291 {
292 throw OrthancException(ErrorCode_BadFileFormat);
293 }
294 }
295
296
297 void OrthancRestApi::RegisterArchive()
298 {
299 Register("/patients/{id}/archive",
300 CreateSingleGet<false /* ZIP */, false /* extended makes no sense in ZIP */>);
301 Register("/studies/{id}/archive",
302 CreateSingleGet<false /* ZIP */, false /* extended makes no sense in ZIP */>);
303 Register("/series/{id}/archive",
304 CreateSingleGet<false /* ZIP */, false /* extended makes no sense in ZIP */>);
305
306 Register("/patients/{id}/archive",
307 CreateSinglePost<false /* ZIP */, false /* extended makes no sense in ZIP */>);
308 Register("/studies/{id}/archive",
309 CreateSinglePost<false /* ZIP */, false /* extended makes no sense in ZIP */>);
310 Register("/series/{id}/archive",
311 CreateSinglePost<false /* ZIP */, false /* extended makes no sense in ZIP */>);
312
313 Register("/patients/{id}/media",
314 CreateSingleGet<true /* media */, false /* not extended by default */>);
315 Register("/studies/{id}/media",
316 CreateSingleGet<true /* media */, false /* not extended by default */>);
317 Register("/series/{id}/media",
318 CreateSingleGet<true /* media */, false /* not extended by default */>);
319
320 Register("/patients/{id}/media",
321 CreateSinglePost<true /* media */, false /* not extended by default */>);
322 Register("/studies/{id}/media",
323 CreateSinglePost<true /* media */, false /* not extended by default */>);
324 Register("/series/{id}/media",
325 CreateSinglePost<true /* media */, false /* not extended by default */>);
326
327 Register("/tools/create-archive",
328 CreateBatch<false /* ZIP */, false /* extended makes no sense in ZIP */>);
329 Register("/tools/create-media",
330 CreateBatch<true /* media */, false /* not extended by default */>);
331 Register("/tools/create-media-extended",
332 CreateBatch<true /* media */, true /* extended by default */>);
333 }
334 }