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.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 }