View Javadoc
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 }