diff --git a/TD4/src/main/java/fr/miage23/filestore/TypeNode.java b/TD4/src/main/java/fr/miage23/filestore/TypeNode.java new file mode 100644 index 0000000000000000000000000000000000000000..d03f39dbf35208aaf0029af778d532bf5b2a7f2a --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/TypeNode.java @@ -0,0 +1,6 @@ +package fr.miage23.filestore; + +public enum TypeNode { + TREE, + BLOB +} diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/CollectionDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/CollectionDto.java index b4fc9b91f79357d5d90044e506fd3c35ed95f765..9836aef3a70ae5f409594176c7074b45466a8f8c 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/dto/CollectionDto.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/dto/CollectionDto.java @@ -14,6 +14,12 @@ public class CollectionDto<T> { values = new ArrayList<>(); } + public CollectionDto(int limit, int offset) { + values = new ArrayList<>(); + this.limit = limit; + this.offset = offset; + } + public List<T> getValues() { return values; } diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/GETNodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/GETNodeDto.java new file mode 100644 index 0000000000000000000000000000000000000000..de238e2f40c1e840fe41715b77c505fb1d77b8c1 --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/api/dto/GETNodeDto.java @@ -0,0 +1,23 @@ +package fr.miage23.filestore.api.dto; + +import fr.miage23.filestore.TypeNode; + +public class GETNodeDto { + private TypeNode type; + private String id; + private String parent; + private String name; + private long size; + private long creation; + private long modification; + + public GETNodeDto(TypeNode type, String id, String parent, String name, long size, long creation, long modification) { + this.type = type; + this.id = id; + this.parent = parent; + this.name = name; + this.size = size; + this.creation = creation; + this.modification = modification; + } +} diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/NodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/InputNodeDto.java similarity index 92% rename from TD4/src/main/java/fr/miage23/filestore/api/dto/NodeDto.java rename to TD4/src/main/java/fr/miage23/filestore/api/dto/InputNodeDto.java index 43c5c73193f6006698e6e173bf0d6e03b37f3d0d..d616d50266a6c2a1f4501fba6281bd259785aa7e 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/dto/NodeDto.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/dto/InputNodeDto.java @@ -6,7 +6,7 @@ import org.jboss.resteasy.annotations.providers.multipart.PartType; import java.io.InputStream; -public class NodeDto { +public class InputNodeDto { @FormParam("name") @PartType(MediaType.TEXT_PLAIN) @@ -15,7 +15,7 @@ public class NodeDto { @PartType(MediaType.APPLICATION_OCTET_STREAM) private InputStream data = null; - public NodeDto() { + public InputNodeDto() { // Default constructor needed for JAX-RS } diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/POSTNodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/POSTNodeDto.java new file mode 100644 index 0000000000000000000000000000000000000000..ce8e8d8e0792dcb86bc56772686d3c2faf44ceb9 --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/api/dto/POSTNodeDto.java @@ -0,0 +1,15 @@ +package fr.miage23.filestore.api.dto; + +import fr.miage23.filestore.TypeNode; + +public class POSTNodeDto { + private String id = null; + private String name = null; + private TypeNode type = null; + + public POSTNodeDto(String id, String name, TypeNode type) { + this.id = id; + this.name = name; + this.type = type; + } +} diff --git a/TD4/src/main/java/fr/miage23/filestore/api/resources/NodesResource.java b/TD4/src/main/java/fr/miage23/filestore/api/resources/NodesResource.java index aed8f5a9b9442f4da3b0c1763eccc5e3990df3c7..ea19e4283ed2fe05eecc3fa8c037dd192f1758c1 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/resources/NodesResource.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/resources/NodesResource.java @@ -5,7 +5,11 @@ import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import fr.miage23.filestore.api.dto.NodeDto; +import fr.miage23.filestore.TypeNode; +import fr.miage23.filestore.api.dto.CollectionDto; +import fr.miage23.filestore.api.dto.GETNodeDto; +import fr.miage23.filestore.api.dto.InputNodeDto; +import fr.miage23.filestore.api.dto.POSTNodeDto; import fr.miage23.filestore.files.FileService; import fr.miage23.filestore.files.InMemoryFileServiceBean; import fr.miage23.filestore.files.entity.Node; @@ -22,9 +26,10 @@ import jakarta.ws.rs.core.UriInfo; import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; import java.io.IOException; -import java.net.URI; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * Resource class for managing nodes in the file storage system. @@ -48,14 +53,52 @@ public class NodesResource { LOGGER.log(Level.INFO, "GET /api/nodes/root"); try { Gson gson = new GsonBuilder() - .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) .create(); - return Response.ok(gson.toJson(service.get("root"), Node.class), MediaType.APPLICATION_JSON).build(); + Node node = service.get("root"); + return Response.ok(gson.toJson( + (new GETNodeDto(node.getType(), node.getId(), node.getParent(), node.getName(), node.getSize(), node.getCreation(), node.getModification())), + GETNodeDto.class), + MediaType.APPLICATION_JSON).build(); } catch (NodeNotFoundException e) { return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); } } + /** + * Retrieves the content of a node identified by the given ID. For tree nodes, it returns a paginated + * list of child nodes. For blob nodes, it returns the binary content of the node. + * + * @param id The ID of the node. + * @param limit The maximum number of child nodes to return (default is 20). + * @param offset The offset for pagination (default is 0). + * @return Response containing the content of the node or a paginated list of child nodes. + */ + @GET + @Path("{id}/content") + @Produces(MediaType.APPLICATION_JSON) + public Response content(@PathParam("id") final String id, @QueryParam("limit") @DefaultValue("10") int limit, @QueryParam("offset") @DefaultValue("0") int offset) throws NodeNotFoundException, NodeTypeException, ContentException { + LOGGER.log(Level.INFO, "GET /api/nodes/" + id + "/content"); + try { + Node node = service.get(id); + if (node.getType().equals(TypeNode.TREE)) { + CollectionDto<Node> dto = new CollectionDto(limit, offset); + List<Node> nodes = service.list(node.getId()); + dto.setValues(nodes.stream().skip(offset).limit(limit).collect(Collectors.toList())); + dto.setSize(nodes.size()); + return Response.ok(dto).build(); + } else { + return Response.ok(service.getContent(id)) + .header("Content-Type", node.getMimetype()) + .header("Content-Length", node.getSize()) + .header("Content-Disposition", "attachment; filename=" + node.getName()).build(); + } + } catch (NodeNotFoundException e) { + return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); + } + } + + /** * Retrieves information about a specific node. * @@ -69,9 +112,13 @@ public class NodesResource { LOGGER.log(Level.INFO, "GET /api/nodes/" + id); try{ Gson gson = new GsonBuilder() - .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) .create(); - return Response.ok(gson.toJson(service.get(id), Node.class), MediaType.APPLICATION_JSON).build(); + Node node = service.get(id); + return Response.ok(gson.toJson( + (new GETNodeDto(node.getType(), node.getId(), node.getParent(), node.getName(), node.getSize(), node.getCreation(), node.getModification())), + GETNodeDto.class), + MediaType.APPLICATION_JSON).build(); } catch (NodeNotFoundException e) { return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); } @@ -98,9 +145,14 @@ public class NodesResource { throw new IllegalArgumentException("A name must be provided"); } Gson gson = new GsonBuilder() - .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) .create(); - return Response.ok(gson.toJson(service.get(newid), Node.class), MediaType.APPLICATION_JSON).build(); + Node node = service.get(newid); + + return Response.ok(gson.toJson( + (new POSTNodeDto(node.getId(), node.getName(), node.getType())), + POSTNodeDto.class), + MediaType.APPLICATION_JSON).build(); } catch (IllegalArgumentException e) { // Transform the IllegalArgumentException into a 400 Bad Request response return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build(); @@ -124,7 +176,7 @@ public class NodesResource { @Path("{id}") @Produces(MediaType.TEXT_HTML) @Consumes(MediaType.MULTIPART_FORM_DATA) - public Response createView(@PathParam("id") final String id, @MultipartForm @Valid NodeDto dto, @Context UriInfo info) { + public Response createView(@PathParam("id") final String id, @MultipartForm @Valid InputNodeDto dto, @Context UriInfo info) { LOGGER.log(Level.INFO, "POST /api/nodes/" + id + " (html)"); try { String newid; @@ -136,9 +188,13 @@ public class NodesResource { throw new IllegalArgumentException("A name must be provided"); } Gson gson = new GsonBuilder() - .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) .create(); - return Response.ok(gson.toJson(service.get(newid), Node.class), MediaType.APPLICATION_JSON).build(); + Node node = service.get(newid); + return Response.ok(gson.toJson( + (new POSTNodeDto(node.getId(), node.getName(), node.getType())), + POSTNodeDto.class), + MediaType.APPLICATION_JSON).build(); } catch (IllegalArgumentException e) { // Transform the IllegalArgumentException into a 400 Bad Request response return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build(); @@ -151,15 +207,15 @@ public class NodesResource { } // Classe EnumTypeAdapter pour la gestion de la sérialisation/désérialisation de l'énumération Node.Type - private static class EnumTypeAdapter extends TypeAdapter<Node.Type> { + private static class EnumTypeAdapter extends TypeAdapter<TypeNode> { @Override - public void write(JsonWriter out, Node.Type value) throws IOException { + public void write(JsonWriter out, TypeNode value) throws IOException { out.value(value.name()); } @Override - public Node.Type read(JsonReader in) throws IOException { - return Node.Type.valueOf(in.nextString()); + public TypeNode read(JsonReader in) throws IOException { + return TypeNode.valueOf(in.nextString()); } } diff --git a/TD4/src/main/java/fr/miage23/filestore/files/InMemoryFileServiceBean.java b/TD4/src/main/java/fr/miage23/filestore/files/InMemoryFileServiceBean.java index 1882b32ae9904352785028992a2d0f93faa3ee07..dad5d5d42bb8f5b8a4001918bfcb73565469a074 100644 --- a/TD4/src/main/java/fr/miage23/filestore/files/InMemoryFileServiceBean.java +++ b/TD4/src/main/java/fr/miage23/filestore/files/InMemoryFileServiceBean.java @@ -1,5 +1,6 @@ package fr.miage23.filestore.files; +import fr.miage23.filestore.TypeNode; import fr.miage23.filestore.files.entity.Node; import fr.miage23.filestore.files.exceptions.*; @@ -31,7 +32,7 @@ public class InMemoryFileServiceBean implements FileService { LOGGER.log(Level.INFO, "Instantiating file service bean"); contents = new HashMap<>(); nodes = new ConcurrentHashMap<>(); - Node root = new Node(Node.Type.TREE, "", ROOT_NODE_ID, "root"); + Node root = new Node(TypeNode.TREE, "", ROOT_NODE_ID, "root"); nodes.put(root.getId(), root); } @@ -61,7 +62,7 @@ public class InMemoryFileServiceBean implements FileService { throw new NodeNotFoundException("unable to find a node with id " + id + " in the storage"); } Node node = nodes.get(id); - if (!node.getType().equals(Node.Type.BLOB)) { + if (!node.getType().equals(TypeNode.BLOB)) { throw new NodeTypeException("only node of type BLOB have content"); } if (!contents.containsKey(node.getContent())) { @@ -78,13 +79,13 @@ public class InMemoryFileServiceBean implements FileService { throw new NodeNotFoundException("Unable to find a node with id: " + pid); } Node pnode = nodes.get(pid); - if (!pnode.getType().equals(Node.Type.TREE)) { + if (!pnode.getType().equals(TypeNode.TREE)) { throw new NodeTypeException("Parent must be a node of type TREE"); } if (nodes.values().stream().filter(n -> n.getParent().equals(pid)).anyMatch(n -> n.getName().equals(name))) { throw new NodeAlreadyExistsException("A node with name: " + name + " already exists in tree with id: " + pid); } - Node node = new Node(Node.Type.TREE, pid, UUID.randomUUID().toString(), name); + Node node = new Node(TypeNode.TREE, pid, UUID.randomUUID().toString(), name); nodes.put(node.getId(), node); pnode.setSize(pnode.getSize()+1); return node.getId(); @@ -98,7 +99,7 @@ public class InMemoryFileServiceBean implements FileService { throw new NodeNotFoundException("Unable to find a parent node with id: " + pid); } Node pnode = nodes.get(pid); - if (!pnode.getType().equals(Node.Type.TREE)) { + if (!pnode.getType().equals(TypeNode.TREE)) { throw new NodeTypeException("Parent must be a node of type TREE"); } if (nodes.values().stream().filter(n -> n.getParent().equals(pid)).anyMatch(n -> n.getName().equals(name))) { @@ -107,7 +108,7 @@ public class InMemoryFileServiceBean implements FileService { String cid = UUID.randomUUID().toString(); try { contents.put(cid, content.readAllBytes()); - Node node = new Node(Node.Type.BLOB, pid, UUID.randomUUID().toString(), name); + Node node = new Node(TypeNode.BLOB, pid, UUID.randomUUID().toString(), name); node.setContent(cid); node.setSize(contents.get(cid).length); node.setMimetype("application/octet-stream"); @@ -127,17 +128,17 @@ public class InMemoryFileServiceBean implements FileService { throw new NodeNotFoundException("Unable to find a parent node with id: " + pid); } Node pnode = nodes.get(pid); - if (!pnode.getType().equals(Node.Type.TREE)) { + if (!pnode.getType().equals(TypeNode.TREE)) { throw new NodeTypeException("Parent must be a node of type TREE"); } Optional<Node> node = nodes.values().stream().filter(n -> n.getParent().equals(pid)).filter(n -> n.getName().equals(name)).findAny(); if (node.isEmpty()) { throw new NodeNotFoundException("A node with name: " + name + " does not exists in tree with id: " + pid); } - if (node.get().getType().equals(Node.Type.TREE) && nodes.values().stream().anyMatch(n -> n.getParent().equals(node.get().getId()))) { + if (node.get().getType().equals(TypeNode.TREE) && nodes.values().stream().anyMatch(n -> n.getParent().equals(node.get().getId()))) { throw new NodeNotEmptyException("The node with name: " + name + " is not empty"); } - if (node.get().getType().equals(Node.Type.BLOB)) { + if (node.get().getType().equals(TypeNode.BLOB)) { contents.remove(node.get().getContent()); } nodes.remove(node.get().getId()); diff --git a/TD4/src/main/java/fr/miage23/filestore/files/entity/Node.java b/TD4/src/main/java/fr/miage23/filestore/files/entity/Node.java index 066d1f1cf4225f01dda9a955f82de5ffba93f97b..f30d814c1653df08b0f0374065378dd787ea8fe2 100644 --- a/TD4/src/main/java/fr/miage23/filestore/files/entity/Node.java +++ b/TD4/src/main/java/fr/miage23/filestore/files/entity/Node.java @@ -1,5 +1,6 @@ package fr.miage23.filestore.files.entity; +import fr.miage23.filestore.TypeNode; import fr.miage23.filestore.files.FileService; import java.io.Serializable; @@ -8,7 +9,7 @@ import java.util.Objects; public class Node implements Comparable<Node>, Serializable { - private Type type; + private TypeNode type; private String id; private long version; private String parent; @@ -24,7 +25,7 @@ public class Node implements Comparable<Node>, Serializable { this.size = 0; } - public Node(Type type, String parent, String id, String name) { + public Node(TypeNode type, String parent, String id, String name) { this(); this.parent = parent; this.type = type; @@ -32,11 +33,11 @@ public class Node implements Comparable<Node>, Serializable { this.name = name; } - public Type getType() { + public TypeNode getType() { return type; } - public void setType(Type type) { + public void setType(TypeNode type) { this.type = type; } @@ -117,19 +118,15 @@ public class Node implements Comparable<Node>, Serializable { } public boolean isFolder() { - return this.type.equals(Type.TREE); + return this.type.equals(TypeNode.TREE); } - public enum Type { - TREE, - BLOB - } @Override public int compareTo(Node o) { if (this.getType().equals(o.getType())) { return this.getName().compareTo(o.getName()); - } else if (this.getType().equals(Type.TREE)){ + } else if (this.getType().equals(TypeNode.TREE)){ return 1; } else { return -1;