diff --git a/TD4/pom.xml b/TD4/pom.xml index 8b89a3cbfea0c29af07b14d2d0cd1d3caa8a7f5e..267a71e28f5f49050f7d7d36122699b1478f2c46 100644 --- a/TD4/pom.xml +++ b/TD4/pom.xml @@ -24,6 +24,31 @@ </developer> </developers> + <repositories> + <repository> + <id>jboss-public-maven-repository</id> + <name>JBoss Public Maven Repository</name> + <url>https://repository.jboss.org/nexus/content/repositories/releases/</url> + <releases> + <enabled>true</enabled> + <updatePolicy>never</updatePolicy> + </releases> + <layout>default</layout> + </repository> + </repositories> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.wildfly.bom</groupId> + <artifactId>wildfly-ee-with-tools</artifactId> + <version>${version.wildfly-bom}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.report.sourceEncoding>UTF-8</project.report.sourceEncoding> @@ -46,6 +71,7 @@ <version.apache-commons-codec>1.15</version.apache-commons-codec> <version.freemarker>2.3.30</version.freemarker> <version.resteasy>6.2.6.Final</version.resteasy> + <version.lombok>1.18.30</version.lombok> </properties> <dependencies> @@ -86,6 +112,12 @@ <artifactId>gson</artifactId> <version>2.8.9</version> <!-- Utilisez la dernière version disponible --> </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>${version.lombok}</version> + <optional>true</optional> + </dependency> </dependencies> <build> diff --git a/TD4/src/main/java/fr/miage23/filestore/api/FilestoreApplication.java b/TD4/src/main/java/fr/miage23/filestore/api/FilestoreApplication.java index a2c2b00b6f69a3779c8d7b4b2065b6ce795d4670..815732c2c58528d06e43019bd73323498a266a3c 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/FilestoreApplication.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/FilestoreApplication.java @@ -1,8 +1,10 @@ package fr.miage23.filestore.api; +import jakarta.servlet.annotation.MultipartConfig; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; +@MultipartConfig // This annotation is needed to use multipart-form-data @ApplicationPath("api") public class FilestoreApplication extends Application { } diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/GETOutputNodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/GETOutputNodeDto.java deleted file mode 100644 index cecd4e528268790c856f938b8283748c4f12d682..0000000000000000000000000000000000000000 --- a/TD4/src/main/java/fr/miage23/filestore/api/dto/GETOutputNodeDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package fr.miage23.filestore.api.dto; - -import fr.miage23.filestore.TypeNode; - -public class GETOutputNodeDto { - private TypeNode type; - private String id; - private String parent; - private String name; - private long size; - private long creation; - private long modification; - - public GETOutputNodeDto(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/InputNodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/InputNodeDto.java index c9233ddb0f4065ce4e13fe5e19ac6c7cd240033b..2b9976716ba6202ca414e26bfadeec6c3f59bae8 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/dto/InputNodeDto.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/dto/InputNodeDto.java @@ -5,15 +5,19 @@ import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import jakarta.ws.rs.FormParam; import jakarta.ws.rs.core.MediaType; +import lombok.Data; +import lombok.NoArgsConstructor; import org.jboss.resteasy.annotations.providers.multipart.PartType; import java.io.InputStream; +@Data +@NoArgsConstructor // Default constructor needed for JAX-RS public class InputNodeDto { @FormParam("name") @PartType(MediaType.TEXT_PLAIN) - @Filename + @Pattern(regexp = "^[^*&%/]+$") private String name; @FormParam("data") @PartType(MediaType.APPLICATION_OCTET_STREAM) @@ -21,33 +25,5 @@ public class InputNodeDto { @Size(max=2000000, message="content size is 2Mo max, for larger content use a multipart-form-data with data parameter") private byte[] content; - public InputNodeDto() { - // Default constructor needed for JAX-RS - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public InputStream getData() { - return data; - } - - public void setData(InputStream data) { - this.data = data; - } - - public byte[] getContent() { - return content; - } - - public void setContent(byte[] content) { - this.content = content; - } - } diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/OutputNodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/OutputNodeDto.java new file mode 100644 index 0000000000000000000000000000000000000000..2899cb3e0ce38732c1fccb1bc39c4860f42b7ef4 --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/api/dto/OutputNodeDto.java @@ -0,0 +1,17 @@ +package fr.miage23.filestore.api.dto; + +import fr.miage23.filestore.files.entity.TypeNode; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class OutputNodeDto { + private TypeNode type; + private String id; + private String parent; + private String name; + private long size; + private long creation; + private long modification; +} diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/POSTOutputNodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/POSTOutputNodeDto.java deleted file mode 100644 index 850577354ac9fc5b6b1bea17b4a4948fd4ef4f9f..0000000000000000000000000000000000000000 --- a/TD4/src/main/java/fr/miage23/filestore/api/dto/POSTOutputNodeDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package fr.miage23.filestore.api.dto; - -import fr.miage23.filestore.TypeNode; - -public class POSTOutputNodeDto { - - private String id = null; - - public POSTOutputNodeDto(String id) { - this.id = id; - } -} diff --git a/TD4/src/main/java/fr/miage23/filestore/api/mapper/NodeHtmlMapper.java b/TD4/src/main/java/fr/miage23/filestore/api/mapper/NodeHtmlMapper.java deleted file mode 100644 index dd5cc3c5484e2f6a28391a1d6504e0fec22d5db9..0000000000000000000000000000000000000000 --- a/TD4/src/main/java/fr/miage23/filestore/api/mapper/NodeHtmlMapper.java +++ /dev/null @@ -1,45 +0,0 @@ -package fr.miage23.filestore.api.mapper; - -import fr.miage23.filestore.files.entity.Node; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.ext.MessageBodyWriter; -import jakarta.ws.rs.ext.Provider; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.Writer; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; - - -@Provider -@Produces(MediaType.TEXT_HTML) -public class NodeHtmlMapper implements MessageBodyWriter<Node> { - - - @Override - public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { - return true; - } - - @Override - public void writeTo(Node node, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException { - Writer writer = new PrintWriter(outputStream); - writer.write("<html><head><title>Filestore</title></head>"); - writer.write("<body><h2>" + node.getName() + "</h2>"); - writer.write("<ul><li>id: " + node.getId() + "</li>"); - writer.write("<li>type: " + node.getType() + "</li>"); - writer.write("<li>size: " + node.getSize() + "</li>"); - writer.write("<li>creation: " + node.getCreation() + "</li>"); - writer.write("<li>modification: " + node.getModification() + "</li>"); - writer.write("</ul></body>"); - writer.flush(); - writer.close(); - } -} - - 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 ceb58e8d9fd668b22d185be416a4b64ef7affce0..9a55b3caf0b52d7b51576ba4061e1771246f92a3 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 @@ -1,24 +1,17 @@ package fr.miage23.filestore.api.resources; -import com.google.gson.Gson; -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.TypeNode; +import fr.miage23.filestore.auth.entity.Profile; +import fr.miage23.filestore.files.entity.TypeNode; import fr.miage23.filestore.api.dto.CollectionDto; -import fr.miage23.filestore.api.dto.GETNodeDto; -import fr.miage23.filestore.api.dto.GETOutputNodeDto; import fr.miage23.filestore.api.dto.InputNodeDto; -import fr.miage23.filestore.api.dto.POSTNodeDto; -import fr.miage23.filestore.api.dto.POSTOutputNodeDto; +import fr.miage23.filestore.api.template.Template; +import fr.miage23.filestore.api.template.TemplateContent; +import fr.miage23.filestore.config.FilestoreConfig; import fr.miage23.filestore.files.FileService; import fr.miage23.filestore.files.InMemoryFileServiceBean; import fr.miage23.filestore.files.entity.Node; -import fr.miage23.filestore.files.exceptions.ContentException; -import fr.miage23.filestore.files.exceptions.NodeAlreadyExistsException; -import fr.miage23.filestore.files.exceptions.NodeNotFoundException; -import fr.miage23.filestore.files.exceptions.NodeTypeException; +import fr.miage23.filestore.files.exceptions.*; +import jakarta.inject.Inject; import jakarta.validation.Valid; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Context; @@ -27,8 +20,13 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -40,42 +38,38 @@ import java.util.stream.Collectors; @Path("nodes") public class NodesResource { - private FileService service = InMemoryFileServiceBean.getInstance(); private static final Logger LOGGER = Logger.getLogger(NodesResource.class.getName()); - @Context - UriInfo uriInfo; // Injectez l'objet UriInfo pour accéder aux informations d'URI. - /** - * Redirects to the default node ("/nodes/root"). - * - * @return Response object for redirection to the default node. - */ + private FileService service = InMemoryFileServiceBean.getInstance(); + @Inject + private FilestoreConfig config; + @GET - public Response redirectToDefaultNode() { + @Produces(MediaType.APPLICATION_JSON) + public Response root(@Context UriInfo uriInfo) throws NodeNotFoundException { LOGGER.log(Level.INFO, "GET /api/nodes/root"); - try { - Gson gson = new GsonBuilder() - .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) - .create(); - Node node = service.get("root"); - return Response.ok(gson.toJson( - (new GETOutputNodeDto(node.getType(), node.getId(), node.getParent(), node.getName(), node.getSize(), node.getCreation(), node.getModification())), - GETOutputNodeDto.class), - MediaType.APPLICATION_JSON).build(); - } catch (NodeNotFoundException e) { - return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); - } + Node node = service.get(""); + URI root = uriInfo.getRequestUriBuilder().path(node.getId()).build(); + return Response.seeOther(root).build(); + } + @GET + @Produces(MediaType.TEXT_HTML) + public Response rootView(@Context UriInfo uriInfo) throws NodeNotFoundException { + LOGGER.log(Level.INFO, "GET /api/nodes (html)"); + Node node = service.get(""); + URI root = uriInfo.getRequestUriBuilder().path(node.getId()).path("content").build(); + return Response.seeOther(root).build(); + } + + @GET + @Path("{id}") + @Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_JSON}) + public Node getNode(@PathParam("id") String id) throws NodeNotFoundException { + LOGGER.log(Level.INFO, "GET /api/nodes/" + id); + Node node = service.get(id); + return node; } - /** - * 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) @@ -96,101 +90,80 @@ public class NodesResource { } } - - /** - * Retrieves information about a specific node. - * - * @param id The ID of the node to retrieve. - * @return The Node object representing the requested node. - */ @GET - @Path("{id}") - @Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_JSON}) - public Response getNode(@PathParam("id") String id) throws NodeNotFoundException { - LOGGER.log(Level.INFO, "GET /api/nodes/" + id); - Gson gson = new GsonBuilder() - .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) - .create(); + @Path("{id}/content") + @Template(name = "files") + @Produces(MediaType.TEXT_HTML) + public Response contentView(@PathParam("id") final String id, @QueryParam("download") @DefaultValue("false") final boolean download) throws NodeNotFoundException, NodeTypeException, ContentException { + LOGGER.log(Level.INFO, "GET /api/nodes/" + id + "/content (html)"); Node node = service.get(id); - return Response.ok(gson.toJson( - (new GETOutputNodeDto(node.getType(), node.getId(), node.getParent(), node.getName(), node.getSize(), node.getCreation(), node.getModification())), - GETOutputNodeDto.class), - MediaType.APPLICATION_JSON).build(); + if (node.getType().equals(TypeNode.TREE)) { + TemplateContent<Map<String, Object>> content = new TemplateContent<>(); + Map<String, Object> value = new HashMap<>(); + value.put("ctx", config.instance().ctx()); + value.put("profile", Profile.ANONYMOUS_PROFILE); + value.put("parent", node); + value.put("path", service.path(id)); + value.put("nodes", service.list(id)); + content.setContent(value); + return Response.ok(content).build(); + } else { + return Response.ok(service.getContent(id)) + .header("Content-Type", node.getMimetype()) + .header("Content-Length", node.getSize()) + .header("Content-Disposition", ((download) ? "attachment; " : "") + "filename=" + node.getName()).build(); + } } - /** - * Creates a new node with the provided parameters. - * - * @param id The ID of the new node, input as a path parameter for a better url uses and transitions. - * @param name The name of the new node. - * @param info The UriInfo object to access URI information. - * @return Response object indicating the success or failure of the operation. - */ @POST @Path("{id}") - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response createNode(@PathParam("id") String id, @FormParam("name") String name, @Context UriInfo info) throws NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException { + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response create(@PathParam("id") final String id, @Valid InputNodeDto dto, @Context UriInfo info) throws NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException, ContentException { LOGGER.log(Level.INFO, "POST /api/nodes/" + id); - String newid; - if (name != null) { - newid = service.add(id, name); + String nid; + if (dto.getContent() != null) { + nid = service.add(id, dto.getName(), new ByteArrayInputStream(dto.getContent())); } else { - throw new IllegalArgumentException("A name must be provided"); + nid = service.add(id, dto.getName()); } - Gson gson = new GsonBuilder() - .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) - .create(); - Node node = service.get(newid); - - return Response.ok(gson.toJson( - (new POSTOutputNodeDto(node.getId())), - POSTOutputNodeDto.class), - MediaType.APPLICATION_JSON).build(); + URI createdUri = info.getBaseUriBuilder().path(NodesResource.class).path(nid).build(); + return Response.created(createdUri).build(); } - /** - * Creates a new node with the provided parameters and optionally uploads content. - * - * @param id The ID of the new node. - * @param dto The CreateNodeDto (DTO = Data Transfer Object) containing information about the new node. - * @param info The UriInfo object to access URI information. - * @return Response object indicating the success or failure of the operation. - */ @POST @Path("{id}") @Produces(MediaType.TEXT_HTML) @Consumes(MediaType.MULTIPART_FORM_DATA) - public Response createView(@PathParam("id") final String id, @MultipartForm @Valid InputNodeDto dto, @Context UriInfo info) throws NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException, ContentException { + public Response createView(@PathParam("id") final String id, @MultipartForm @Valid InputNodeDto dto, @Context UriInfo info) throws NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException, ContentException, IOException { LOGGER.log(Level.INFO, "POST /api/nodes/" + id + " (html)"); - String newid; - if (dto.getName() != null && dto.getData() != null){ - newid = service.add(id, dto.getName(), dto.getData()); - } else if (dto.getName() != null){ - newid = service.add(id, dto.getName()); + if (dto.getData() != null) { + service.add(id, dto.getName(), dto.getData()); } else { - throw new IllegalArgumentException("A name must be provided"); + service.add(id, dto.getName()); } - Gson gson = new GsonBuilder() - .registerTypeAdapter(TypeNode.class, new EnumTypeAdapter()) - .create(); - Node node = service.get(newid); - return Response.ok(gson.toJson( - (new POSTOutputNodeDto(node.getId())), - POSTOutputNodeDto.class), - MediaType.APPLICATION_JSON).build(); + URI createdUri = info.getBaseUriBuilder().path(NodesResource.class).path(id).path("content").build(); + return Response.seeOther(createdUri).build(); } - // Classe EnumTypeAdapter pour la gestion de la sérialisation/désérialisation de l'énumération TypeNode - private static class EnumTypeAdapter extends TypeAdapter<TypeNode> { - @Override - public void write(JsonWriter out, TypeNode value) throws IOException { - out.value(value.name()); - } + @PUT + @Path("{id}/{name}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response update(@PathParam("id") final String id, @PathParam("name") String name, @FormParam("data") InputStream data) throws NodeNotEmptyException, NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException, ContentException { + LOGGER.log(Level.INFO, "PUT /api/nodes/" + id + "/" + name); + service.remove(id, name); + service.add(id, name, data); + return Response.noContent().build(); + } - @Override - public TypeNode read(JsonReader in) throws IOException { - return TypeNode.valueOf(in.nextString()); - } + @DELETE + @Path("{id}/{name}") + @Produces(MediaType.APPLICATION_JSON) + public Response delete(@PathParam("id") final String id, @PathParam("name") final String name) throws NodeNotEmptyException, NodeNotFoundException, NodeTypeException { + LOGGER.log(Level.INFO, "DELETE /api/nodes/" + name); + service.remove(id, name); + return Response.noContent().build(); } } diff --git a/TD4/src/main/java/fr/miage23/filestore/api/validation/ValidationPattern.java b/TD4/src/main/java/fr/miage23/filestore/api/validation/ValidationPattern.java index 8b6ef2b5c99690a91066af00de6d4de548aa6c01..465b82b0dc3a484740d39c992ccaaba7754f91c2 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/validation/ValidationPattern.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/validation/ValidationPattern.java @@ -1,7 +1,6 @@ package fr.miage23.filestore.api.validation; public class ValidationPattern { - // On interdit les caractères interdits par windows dans les noms de fichiers - public static final String FILE_PATTERN = "^[^\\/:*?\"<>|]+$"; + public static final String FILE_PATTERN = "^[^*&%/\\~]+$"; } diff --git a/TD4/src/main/java/fr/miage23/filestore/auth/AuthenticationService.java b/TD4/src/main/java/fr/miage23/filestore/auth/AuthenticationService.java new file mode 100644 index 0000000000000000000000000000000000000000..9330f030cb7fc40553582e00e1365e8df09c8cdf --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/auth/AuthenticationService.java @@ -0,0 +1,16 @@ +package fr.miage23.filestore.auth; + +import fr.miage23.filestore.auth.entity.Profile; + +public interface AuthenticationService { + + String UNAUTHENTIFIED_IDENTIFIER = "anonymous"; + + boolean isAuthentified(); + + String getConnectedIdentifier(); + + Profile getConnectedProfile(); + +} + diff --git a/TD4/src/main/java/fr/miage23/filestore/auth/entity/Profile.java b/TD4/src/main/java/fr/miage23/filestore/auth/entity/Profile.java new file mode 100644 index 0000000000000000000000000000000000000000..030ce5546e074d78e1bbe9bdb941311fcd136c78 --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/auth/entity/Profile.java @@ -0,0 +1,80 @@ +package fr.miage23.filestore.auth.entity; + +import fr.miage23.filestore.auth.AuthenticationService; +import org.apache.commons.codec.digest.DigestUtils; + +public class Profile { + + public static final Profile ANONYMOUS_PROFILE = new Profile(AuthenticationService.UNAUTHENTIFIED_IDENTIFIER, "anonymous", "Anonymous User", "user@anonymous.org", false); + + private String id; + private String username; + private String fullname; + private String email; + private boolean owner; + + public Profile() { + } + + public Profile(String id, String username, String fullname, String email, boolean owner) { + this.id = id; + this.username = username; + this.fullname = fullname; + this.email = email; + this.owner = owner; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFullname() { + return fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getGravatarHash() { + return DigestUtils.md5Hex(email); + } + + public boolean isOwner() { + return owner; + } + + public void setOwner(boolean owner) { + this.owner = owner; + } + + @Override + public String toString() { + return "Profile{" + + "id='" + id + '\'' + + ", username='" + username + '\'' + + ", fullname='" + fullname + '\'' + + ", email='" + email + '\'' + + '}'; + } +} diff --git a/TD4/src/main/java/fr/miage23/filestore/config/FilestoreConfig.java b/TD4/src/main/java/fr/miage23/filestore/config/FilestoreConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..bf4e7cab05f14dec3d7ae9f78d0a87b26fc1db36 --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/config/FilestoreConfig.java @@ -0,0 +1,25 @@ +package fr.miage23.filestore.config; + +import java.nio.file.Path; + +public interface FilestoreConfig { + + String owner(); + + Path home(); + + InstanceConfig instance(); + + interface InstanceConfig { + + boolean https(); + + String host(); + + int port(); + + String ctx(); + + } + +} diff --git a/TD4/src/main/java/fr/miage23/filestore/config/FilestoreConfigBean.java b/TD4/src/main/java/fr/miage23/filestore/config/FilestoreConfigBean.java new file mode 100644 index 0000000000000000000000000000000000000000..d5f23fc53b3447a764db81cb7ded5c4f4012ae67 --- /dev/null +++ b/TD4/src/main/java/fr/miage23/filestore/config/FilestoreConfigBean.java @@ -0,0 +1,119 @@ +package fr.miage23.filestore.config; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import java.util.logging.Logger; + +@ApplicationScoped +public class FilestoreConfigBean implements FilestoreConfig { + + private static final Logger LOGGER = Logger.getLogger(FilestoreConfig.class.getName()); + + private Path home; + private String owner; + private LocalInstanceConfig instance; + + public FilestoreConfigBean() { + } + + @PostConstruct + public void init() { + LOGGER.log(Level.INFO, "Initializing config"); + home = Paths.get(getConfigValue("filestoreHome", System.getProperty("user.home").concat(File.separator).concat(".filestore"))); + LOGGER.log(Level.INFO, "home set to: " + home); + owner = getConfigValue("filestoreOwner", "admin"); + LOGGER.log(Level.INFO, "owner set to: " + owner); + instance = new LocalInstanceConfig(Boolean.getBoolean(getConfigValue("filestoreHttps", "false")), + getConfigValue("filestoreHost", "localhost"), + Integer.parseInt(getConfigValue("filestorePort", "8080")), + getConfigValue("filestoreCtx", "/filestore")); + LOGGER.log(Level.INFO, "instance set to: " + instance); + } + + @Override + public InstanceConfig instance() { + return instance; + } + + @Override + public Path home() { + return home; + } + + @Override + public String owner() { + return owner; + } + + static class LocalInstanceConfig implements InstanceConfig { + + private boolean https; + private String host; + private int port; + + private String ctx; + + public LocalInstanceConfig(boolean https, String host, int port, String ctx) { + this.https = https; + this.host = host; + this.port = port; + this.ctx = ctx; + } + + @Override + public boolean https() { + return https; + } + + @Override + public String host() { + return host; + } + + @Override + public int port() { + return port; + } + + @Override + public String ctx() { + return ctx; + } + + @Override + public String toString() { + return "LocalInstanceConfig{" + + "https=" + https + + ", host='" + host + '\'' + + ", port=" + port + + ", ctx='" + ctx + '\'' + + '}'; + } + } + + private String getConfigValue(String key, String defaultValue) { + if ( System.getenv(camelToEnv(key)) != null ) { + LOGGER.log(Level.FINE, "config value loaded from system environment: " + System.getenv(camelToEnv(key))); + return System.getenv(camelToEnv(key)); + } else if ( System.getProperty(camelToProp(key)) != null ) { + LOGGER.log(Level.FINE, "config value loaded from system java property: " + System.getProperty(camelToProp(key))); + return System.getProperty(camelToProp(key)); + } else { + LOGGER.log(Level.FINE, "config value default: " + defaultValue); + return defaultValue; + } + } + + private static String camelToEnv(String var) { + return var.replaceAll("([a-z])([A-Z]+)", "$1_$2").toUpperCase(); + } + + private static String camelToProp(String var) { + return var.replaceAll("([a-z])([A-Z]+)", "$1.$2").toLowerCase(); + } +} diff --git a/TD4/src/main/java/fr/miage23/filestore/files/FileService.java b/TD4/src/main/java/fr/miage23/filestore/files/FileService.java index cd636788f98bc3c3ad43c182626f146496c78f6d..13354663ee3a98529f85665c59c8a2492e241e53 100644 --- a/TD4/src/main/java/fr/miage23/filestore/files/FileService.java +++ b/TD4/src/main/java/fr/miage23/filestore/files/FileService.java @@ -13,6 +13,8 @@ public interface FileService { List<Node> list(String id) throws NodeNotFoundException; + List<Node> path(String id) throws NodeNotFoundException; + Node get(String id) throws NodeNotFoundException; InputStream getContent(String id) throws NodeNotFoundException, NodeTypeException, ContentException; 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 dad5d5d42bb8f5b8a4001918bfcb73565469a074..9a92edbe5de060975c139747785ffa0b1687fdb8 100644 --- a/TD4/src/main/java/fr/miage23/filestore/files/InMemoryFileServiceBean.java +++ b/TD4/src/main/java/fr/miage23/filestore/files/InMemoryFileServiceBean.java @@ -1,7 +1,7 @@ package fr.miage23.filestore.files; -import fr.miage23.filestore.TypeNode; import fr.miage23.filestore.files.entity.Node; +import fr.miage23.filestore.files.entity.TypeNode; import fr.miage23.filestore.files.exceptions.*; import java.io.ByteArrayInputStream; @@ -46,13 +46,32 @@ public class InMemoryFileServiceBean implements FileService { return nodes.values().stream().filter(n -> n.getParent().equals(pid)).collect(Collectors.toList()); } + @Override + public List<Node> path(String id) throws NodeNotFoundException { + LOGGER.log(Level.FINE, "Get path for node with id: " + id); + List<Node> path = new ArrayList<>(); + while (!id.equals(ROOT_NODE_ID)) { + if (!nodes.containsKey(id)) { + throw new NodeNotFoundException("unable to find a node with id " + id + " in the storage"); + } + Node node = nodes.get(id); + path.add(node); + id = node.getParent(); + } + Collections.reverse(path); + LOGGER.log(Level.FINE, "path: " + path.stream().map(Node::getName).collect(Collectors.joining(" > "))); + return path; + } + + @Override public Node get(String id) throws NodeNotFoundException { - LOGGER.log(Level.INFO, "Getting node with id: " + id); - if (!nodes.containsKey(id)) { - throw new NodeNotFoundException("unable to find a node with id " + id + " in the storage"); + String pid = (id == null || id.isEmpty())? ROOT_NODE_ID:id; + LOGGER.log(Level.INFO, "Getting node with id: " + pid); + if (!nodes.containsKey(pid)) { + throw new NodeNotFoundException("unable to find a node with id " + pid + " in the storage"); } - return nodes.get(id); + return nodes.get(pid); } @Override @@ -86,6 +105,7 @@ public class InMemoryFileServiceBean implements FileService { throw new NodeAlreadyExistsException("A node with name: " + name + " already exists in tree with id: " + pid); } Node node = new Node(TypeNode.TREE, pid, UUID.randomUUID().toString(), name); + node.setMimetype(TREE_NODE_MIMETYPE); nodes.put(node.getId(), node); pnode.setSize(pnode.getSize()+1); return node.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 f30d814c1653df08b0f0374065378dd787ea8fe2..011ce4095ee14dc44a32887779074d4f63407744 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,12 +1,13 @@ package fr.miage23.filestore.files.entity; -import fr.miage23.filestore.TypeNode; import fr.miage23.filestore.files.FileService; +import lombok.Data; import java.io.Serializable; import java.util.Comparator; import java.util.Objects; +@Data public class Node implements Comparable<Node>, Serializable { private TypeNode type; @@ -33,86 +34,6 @@ public class Node implements Comparable<Node>, Serializable { this.name = name; } - public TypeNode getType() { - return type; - } - - public void setType(TypeNode type) { - this.type = type; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public long getVersion() { - return version; - } - - public void setVersion(long version) { - this.version = version; - } - - public String getParent() { - return parent; - } - - public void setParent(String parent) { - this.parent = parent; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getMimetype() { - return mimetype; - } - - public void setMimetype(String mimetype) { - this.mimetype = mimetype; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public long getCreation() { - return creation; - } - - public void setCreation(long creation) { - this.creation = creation; - } - - public long getModification() { - return modification; - } - - public void setModification(long modification) { - this.modification = modification; - } - public boolean isRoot() { return this.id.equals(FileService.ROOT_NODE_ID); } @@ -121,7 +42,6 @@ public class Node implements Comparable<Node>, Serializable { return this.type.equals(TypeNode.TREE); } - @Override public int compareTo(Node o) { if (this.getType().equals(o.getType())) { @@ -146,6 +66,22 @@ public class Node implements Comparable<Node>, Serializable { return Objects.hash(type, id, name, content, mimetype, creation, modification); } + @Override + public String toString() { + return "Node{" + + "type=" + type + + ", id='" + id + '\'' + + ", version=" + version + + ", parent='" + parent + '\'' + + ", name='" + name + '\'' + + ", mimetype='" + mimetype + '\'' + + ", size=" + size + + ", creation=" + creation + + ", modification=" + modification + + ", content='" + content + '\'' + + '}'; + } + public static class NameComparatorAsc implements Comparator<Node> { @Override public int compare(Node o1, Node o2) { diff --git a/TD4/src/main/java/fr/miage23/filestore/TypeNode.java b/TD4/src/main/java/fr/miage23/filestore/files/entity/TypeNode.java similarity index 51% rename from TD4/src/main/java/fr/miage23/filestore/TypeNode.java rename to TD4/src/main/java/fr/miage23/filestore/files/entity/TypeNode.java index d03f39dbf35208aaf0029af778d532bf5b2a7f2a..5f235677f04a28be9839fcfa7e7da269b9581dae 100644 --- a/TD4/src/main/java/fr/miage23/filestore/TypeNode.java +++ b/TD4/src/main/java/fr/miage23/filestore/files/entity/TypeNode.java @@ -1,4 +1,4 @@ -package fr.miage23.filestore; +package fr.miage23.filestore.files.entity; public enum TypeNode { TREE, diff --git a/TD4/src/main/resources/ValidationMessages.properties b/TD4/src/main/resources/ValidationMessages.properties index dc0d43f4b21944c4c8ec8b96edc56ee4fbc56c0f..a58bc6359acb30ffa67e0ccf9b6d735185ef54da 100644 --- a/TD4/src/main/resources/ValidationMessages.properties +++ b/TD4/src/main/resources/ValidationMessages.properties @@ -1 +1 @@ -invalid.filename=The filename contains forbidden characters +invalid.filename=Le nom du fichier contient des caract�res interdits