1 package de.dlr.shepard.data.file.endpoints;
2
3 import de.dlr.shepard.auth.permission.io.PermissionsIO;
4 import de.dlr.shepard.auth.permission.model.Roles;
5 import de.dlr.shepard.auth.permission.services.PermissionsService;
6 import de.dlr.shepard.common.filters.Subscribable;
7 import de.dlr.shepard.common.util.Constants;
8 import de.dlr.shepard.common.util.QueryParamHelper;
9 import de.dlr.shepard.data.ContainerAttributes;
10 import de.dlr.shepard.data.file.entities.ShepardFile;
11 import de.dlr.shepard.data.file.io.FileContainerIO;
12 import de.dlr.shepard.data.file.services.FileContainerService;
13 import jakarta.enterprise.context.RequestScoped;
14 import jakarta.inject.Inject;
15 import jakarta.validation.Valid;
16 import jakarta.validation.constraints.NotBlank;
17 import jakarta.validation.constraints.NotNull;
18 import jakarta.validation.constraints.Positive;
19 import jakarta.validation.constraints.PositiveOrZero;
20 import jakarta.ws.rs.Consumes;
21 import jakarta.ws.rs.DELETE;
22 import jakarta.ws.rs.GET;
23 import jakarta.ws.rs.POST;
24 import jakarta.ws.rs.PUT;
25 import jakarta.ws.rs.Path;
26 import jakarta.ws.rs.PathParam;
27 import jakarta.ws.rs.Produces;
28 import jakarta.ws.rs.QueryParam;
29 import jakarta.ws.rs.core.Context;
30 import jakarta.ws.rs.core.MediaType;
31 import jakarta.ws.rs.core.Response;
32 import jakarta.ws.rs.core.Response.Status;
33 import jakarta.ws.rs.core.SecurityContext;
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.util.ArrayList;
39 import org.eclipse.microprofile.openapi.annotations.Operation;
40 import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
41 import org.eclipse.microprofile.openapi.annotations.media.Content;
42 import org.eclipse.microprofile.openapi.annotations.media.Schema;
43 import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
44 import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
45 import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
46 import org.eclipse.microprofile.openapi.annotations.tags.Tag;
47 import org.jboss.resteasy.reactive.RestForm;
48 import org.jboss.resteasy.reactive.multipart.FileUpload;
49
50 @Consumes(MediaType.APPLICATION_JSON)
51 @Produces(MediaType.APPLICATION_JSON)
52 @Path(Constants.FILE_CONTAINERS)
53 @RequestScoped
54 public class FileRest {
55
56 @Inject
57 FileContainerService fileContainerService;
58
59 @Inject
60 PermissionsService permissionsService;
61
62 @Context
63 private SecurityContext securityContext;
64
65 @GET
66 @Tag(name = Constants.FILE_CONTAINER)
67 @Operation(description = "Get all file containers")
68 @APIResponse(
69 description = "ok",
70 responseCode = "200",
71 content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = FileContainerIO.class))
72 )
73 @APIResponse(responseCode = "400", description = "bad request")
74 @APIResponse(responseCode = "401", description = "not authorized")
75 @APIResponse(responseCode = "403", description = "forbidden")
76 @APIResponse(responseCode = "404", description = "not found")
77 @Parameter(name = Constants.QP_NAME)
78 @Parameter(name = Constants.QP_PAGE)
79 @Parameter(name = Constants.QP_SIZE)
80 @Parameter(name = Constants.QP_ORDER_BY_ATTRIBUTE)
81 @Parameter(name = Constants.QP_ORDER_DESC)
82 public Response getAllFileContainers(
83 @QueryParam(Constants.QP_NAME) String name,
84 @QueryParam(Constants.QP_PAGE) @PositiveOrZero Integer page,
85 @QueryParam(Constants.QP_SIZE) @Positive Integer size,
86 @QueryParam(Constants.QP_ORDER_BY_ATTRIBUTE) ContainerAttributes orderBy,
87 @QueryParam(Constants.QP_ORDER_DESC) Boolean orderDesc
88 ) {
89 var params = new QueryParamHelper();
90 if (name != null) params = params.withName(name);
91 if (page != null && size != null) params = params.withPageAndSize(page, size);
92 if (orderBy != null) params = params.withOrderByAttribute(orderBy, orderDesc);
93 var containers = fileContainerService.getAllContainers(params);
94 var result = new ArrayList<FileContainerIO>(containers.size());
95 for (var container : containers) {
96 result.add(new FileContainerIO(container));
97 }
98 return Response.ok(result).build();
99 }
100
101 @GET
102 @Path("/{" + Constants.FILE_CONTAINER_ID + "}")
103 @Tag(name = Constants.FILE_CONTAINER)
104 @Operation(description = "Get file container")
105 @APIResponse(
106 description = "ok",
107 responseCode = "200",
108 content = @Content(schema = @Schema(implementation = FileContainerIO.class))
109 )
110 @APIResponse(responseCode = "400", description = "bad request")
111 @APIResponse(responseCode = "401", description = "not authorized")
112 @APIResponse(responseCode = "403", description = "forbidden")
113 @APIResponse(responseCode = "404", description = "not found")
114 @Parameter(name = Constants.FILE_CONTAINER_ID)
115 public Response getFileContainer(
116 @PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId
117 ) {
118 var result = fileContainerService.getContainer(fileContainerId);
119 return Response.ok(new FileContainerIO(result)).build();
120 }
121
122 @POST
123 @Tag(name = Constants.FILE_CONTAINER)
124 @Operation(description = "Create a new file container")
125 @APIResponse(
126 description = "created",
127 responseCode = "201",
128 content = @Content(schema = @Schema(implementation = FileContainerIO.class))
129 )
130 @APIResponse(responseCode = "400", description = "bad request")
131 @APIResponse(responseCode = "401", description = "not authorized")
132 @APIResponse(responseCode = "403", description = "forbidden")
133 @APIResponse(responseCode = "404", description = "not found")
134 public Response createFileContainer(
135 @RequestBody(
136 required = true,
137 content = @Content(schema = @Schema(implementation = FileContainerIO.class))
138 ) @Valid FileContainerIO fileContainer
139 ) {
140 var result = fileContainerService.createContainer(fileContainer);
141 return Response.ok(new FileContainerIO(result)).status(Status.CREATED).build();
142 }
143
144 @DELETE
145 @Path("/{" + Constants.FILE_CONTAINER_ID + "}")
146 @Subscribable
147 @Tag(name = Constants.FILE_CONTAINER)
148 @Operation(description = "Delete file container")
149 @APIResponse(description = "deleted", responseCode = "204")
150 @APIResponse(responseCode = "400", description = "bad request")
151 @APIResponse(responseCode = "401", description = "not authorized")
152 @APIResponse(responseCode = "403", description = "forbidden")
153 @APIResponse(responseCode = "404", description = "not found")
154 @Parameter(name = Constants.FILE_CONTAINER_ID)
155 public Response deleteFileContainer(
156 @PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId
157 ) {
158 fileContainerService.deleteContainer(fileContainerId);
159 return Response.status(Status.NO_CONTENT).build();
160 }
161
162 @GET
163 @Path("/{" + Constants.FILE_CONTAINER_ID + "}/payload")
164 @Tag(name = Constants.FILE_CONTAINER)
165 @Operation(description = "Get files")
166 @APIResponse(
167 description = "ok",
168 responseCode = "200",
169 content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = ShepardFile.class))
170 )
171 @APIResponse(responseCode = "400", description = "bad request")
172 @APIResponse(responseCode = "401", description = "not authorized")
173 @APIResponse(responseCode = "403", description = "forbidden")
174 @APIResponse(responseCode = "404", description = "not found")
175 @Parameter(name = Constants.FILE_CONTAINER_ID)
176 public Response getAllFiles(@PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId) {
177 var payload = fileContainerService.getContainer(fileContainerId).getFiles();
178 return Response.ok(payload).build();
179 }
180
181 @GET
182 @Path("/{" + Constants.FILE_CONTAINER_ID + "}/payload/{" + Constants.OID + "}")
183 @Produces({ MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON })
184 @Tag(name = Constants.FILE_CONTAINER)
185 @Operation(description = "Get file")
186 @APIResponse(
187 description = "ok",
188 responseCode = "200",
189 content = @Content(
190 mediaType = MediaType.APPLICATION_OCTET_STREAM,
191 schema = @Schema(type = SchemaType.STRING, format = "binary")
192 )
193 )
194 @APIResponse(responseCode = "400", description = "bad request")
195 @APIResponse(responseCode = "401", description = "not authorized")
196 @APIResponse(responseCode = "403", description = "forbidden")
197 @APIResponse(responseCode = "404", description = "not found")
198 @Parameter(name = Constants.FILE_CONTAINER_ID)
199 @Parameter(name = Constants.OID)
200 public Response getFile(
201 @PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId,
202 @PathParam(Constants.OID) @NotBlank String oid
203 ) {
204 var payload = fileContainerService.getFile(fileContainerId, oid);
205 return Response.ok(payload.getInputStream(), MediaType.APPLICATION_OCTET_STREAM)
206 .header("Content-Disposition", "attachment; filename=\"" + payload.getName() + "\"")
207 .header("Content-Length", payload.getSize())
208 .build();
209 }
210
211 @DELETE
212 @Path("/{" + Constants.FILE_CONTAINER_ID + "}/payload/{" + Constants.OID + "}")
213 @Subscribable
214 @Tag(name = Constants.FILE_CONTAINER)
215 @Operation(description = "Delete file")
216 @APIResponse(description = "ok", responseCode = "204")
217 @APIResponse(responseCode = "400", description = "bad request")
218 @APIResponse(responseCode = "401", description = "not authorized")
219 @APIResponse(responseCode = "403", description = "forbidden")
220 @APIResponse(responseCode = "404", description = "not found")
221 @Parameter(name = Constants.FILE_CONTAINER_ID)
222 @Parameter(name = Constants.OID)
223 public Response deleteFile(
224 @PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId,
225 @PathParam(Constants.OID) @NotBlank String oid
226 ) {
227 fileContainerService.deleteFile(fileContainerId, oid);
228 return Response.status(Status.NO_CONTENT).build();
229 }
230
231 @POST
232 @Consumes(MediaType.MULTIPART_FORM_DATA)
233 @Tag(name = Constants.FILE_CONTAINER)
234 @Operation(description = "Upload a new file")
235 @APIResponse(
236 description = "created",
237 responseCode = "201",
238 content = @Content(schema = @Schema(implementation = ShepardFile.class))
239 )
240 @APIResponse(responseCode = "400", description = "bad request")
241 @APIResponse(responseCode = "401", description = "not authorized")
242 @APIResponse(responseCode = "403", description = "forbidden")
243 @APIResponse(responseCode = "404", description = "not found")
244 @Path("/{" + Constants.FILE_CONTAINER_ID + "}/payload")
245 @Subscribable
246 @Parameter(name = Constants.FILE_CONTAINER_ID)
247 public Response createFile(
248 @PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId,
249 MultipartBodyFileUpload body
250 ) {
251 String fileName = body.fileUpload != null ? body.fileUpload.fileName() : null;
252 String filePath = body.fileUpload != null ? body.fileUpload.uploadedFile().toString() : null;
253
254 if (filePath == null) {
255 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
256 }
257
258 File file = new File(filePath);
259 try (InputStream fileInputStream = new FileInputStream(file)) {
260 var result = fileContainerService.createFile(fileContainerId, fileName, fileInputStream);
261 return result != null
262 ? Response.status(Status.CREATED).entity(result).build()
263 : Response.status(Status.INTERNAL_SERVER_ERROR).build();
264 } catch (IOException e) {
265 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
266 }
267 }
268
269 @Schema(type = SchemaType.STRING, format = "binary", description = "File which you want to upload")
270 public interface UploadItemSchema {}
271
272 public class UploadFormSchema {
273
274 @Schema(required = true)
275 public UploadItemSchema file;
276 }
277
278 @Schema(implementation = UploadFormSchema.class)
279 public static class MultipartBodyFileUpload {
280
281 @RestForm(Constants.FILE)
282 public FileUpload fileUpload;
283 }
284
285 @GET
286 @Path("/{" + Constants.FILE_CONTAINER_ID + "}/" + Constants.PERMISSIONS)
287 @Tag(name = Constants.FILE_CONTAINER)
288 @Operation(description = "Get permissions")
289 @APIResponse(
290 description = "ok",
291 responseCode = "200",
292 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
293 )
294 @APIResponse(responseCode = "400", description = "bad request")
295 @APIResponse(responseCode = "401", description = "not authorized")
296 @APIResponse(responseCode = "403", description = "forbidden")
297 @APIResponse(responseCode = "404", description = "not found")
298 @Parameter(name = Constants.FILE_CONTAINER_ID)
299 public Response getFilePermissions(
300 @PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId
301 ) {
302 var perms = fileContainerService.getContainerPermissions(fileContainerId);
303 return Response.ok(new PermissionsIO(perms)).build();
304 }
305
306 @PUT
307 @Path("/{" + Constants.FILE_CONTAINER_ID + "}/" + Constants.PERMISSIONS)
308 @Tag(name = Constants.FILE_CONTAINER)
309 @Operation(description = "Edit permissions")
310 @APIResponse(
311 description = "ok",
312 responseCode = "200",
313 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
314 )
315 @APIResponse(responseCode = "400", description = "bad request")
316 @APIResponse(responseCode = "401", description = "not authorized")
317 @APIResponse(responseCode = "403", description = "forbidden")
318 @APIResponse(responseCode = "404", description = "not found")
319 @Parameter(name = Constants.FILE_CONTAINER_ID)
320 public Response editFilePermissions(
321 @PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId,
322 @RequestBody(
323 required = true,
324 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
325 ) @Valid PermissionsIO permissions
326 ) {
327 var perms = fileContainerService.updateContainerPermissions(permissions, fileContainerId);
328 return Response.ok(new PermissionsIO(perms)).build();
329 }
330
331 @GET
332 @Path("/{" + Constants.FILE_CONTAINER_ID + "}/" + Constants.ROLES)
333 @Tag(name = Constants.FILE_CONTAINER)
334 @Operation(description = "Get roles")
335 @APIResponse(
336 description = "ok",
337 responseCode = "200",
338 content = @Content(schema = @Schema(implementation = Roles.class))
339 )
340 @APIResponse(responseCode = "400", description = "bad request")
341 @APIResponse(responseCode = "401", description = "not authorized")
342 @APIResponse(responseCode = "403", description = "forbidden")
343 @APIResponse(responseCode = "404", description = "not found")
344 @Parameter(name = Constants.FILE_CONTAINER_ID)
345 public Response getFileRoles(@PathParam(Constants.FILE_CONTAINER_ID) @NotNull @PositiveOrZero Long fileContainerId) {
346 var roles = fileContainerService.getContainerRoles(fileContainerId);
347 return Response.ok(roles).build();
348 }
349 }