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