diff --git a/TD4/pom.xml b/TD4/pom.xml index 8ab4c454384bb934a9d9b92cb3c0510899be608e..8b89a3cbfea0c29af07b14d2d0cd1d3caa8a7f5e 100644 --- a/TD4/pom.xml +++ b/TD4/pom.xml @@ -9,30 +9,83 @@ <version>1.0-SNAPSHOT</version> <packaging>war</packaging> + <name>The FileStore Application</name> + <developers> + <developer> + <id>beb</id> + <name>Lucas BERTRAND-CHRISTEN</name> + <email>l.b-c@hotmail.com</email> + <organization>Institut du Digital, du Management et de la Cognition</organization> + <organizationUrl>https://idmc.univ-lorraine.fr/</organizationUrl> + <roles> + <role>developer</role> + </roles> + <timezone>Europe/Paris</timezone> + </developer> + </developers> + <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.report.sourceEncoding>UTF-8</project.report.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + + <maven.compiler.target>17</maven.compiler.target> + <maven.compiler.source>17</maven.compiler.source> <maven.compiler.release>17</maven.compiler.release> - <jakartaee-api.version>10.0.0</jakartaee-api.version> - <wildfly.version>29.0.1.Final</wildfly.version> - <compiler-plugin.version>3.11.0</compiler-plugin.version> - <war-plugin.version>3.4.0</war-plugin.version> - <wildfly-plugin.version>4.2.0.Final</wildfly-plugin.version> + + <path.wildfly>C:\Users\LB-C\Bureau\Logiciels\wildfly-29.0.1.Final</path.wildfly> + + <version.jakartaee-api>10.0.0</version.jakartaee-api> + <version.wildfly>29.0.1.Final</version.wildfly> + <version.compiler-plugin>3.11.0</version.compiler-plugin> + <version.war-plugin>3.4.0</version.war-plugin> + <version.wildfly-plugin>4.2.0.Final</version.wildfly-plugin> + <version.wildfly-bom>30.0.0.Final</version.wildfly-bom> + <version.tika>2.5.0</version.tika> + <version.apache-commons-io>2.11.0</version.apache-commons-io> + <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> </properties> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-api</artifactId> - <version>${jakartaee-api.version}</version> + <version>${version.jakartaee-api}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-multipart-provider</artifactId> - <version>6.2.6.Final</version> + <version>${version.resteasy}</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.freemarker</groupId> + <artifactId>freemarker</artifactId> + <version>${version.freemarker}</version> + </dependency> + <dependency> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + <version>${version.apache-commons-codec}</version> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>${version.apache-commons-io}</version> + </dependency> + <dependency> + <groupId>org.apache.tika</groupId> + <artifactId>tika-core</artifactId> + <version>${version.tika}</version> + </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.8.9</version> <!-- Utilisez la dernière version disponible --> + </dependency> </dependencies> <build> @@ -41,12 +94,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>${compiler-plugin.version}</version> + <version>${version.compiler-plugin}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> - <version>${war-plugin.version}</version> + <version>${version.war-plugin}</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> @@ -56,11 +109,11 @@ <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> - <version>${wildfly-plugin.version}</version> + <version>${version.wildfly-plugin}</version> <configuration> - <version>${wildfly.version}</version> + <version>${version.wildfly}</version> <server-config>standalone-full.xml</server-config> - <jbossHome>C:\Users\LB-C\Bureau\Logiciels\wildfly-29.0.1.Final</jbossHome> + <jbossHome>${path.wildfly}</jbossHome> </configuration> </plugin> </plugins> 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 8cea47fbb4b590d08735f3c2b2429c3ec2b83293..b4fc9b91f79357d5d90044e506fd3c35ed95f765 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 @@ -6,15 +6,12 @@ import java.util.ArrayList; public class CollectionDto<T> { private List<T> values; - private int limit; - private int offset; - private long size; + private int limit = -1; + private int offset = -1; + private long size = -1; public CollectionDto() { values = new ArrayList<>(); - limit = -1; - offset = -1; - size = -1; } public List<T> getValues() { @@ -25,6 +22,12 @@ public class CollectionDto<T> { this.values = values; } + public void addValues(List<T> values) { + for (T value : values) { + this.values.add(value); + } + } + public int getLimit() { return limit; } diff --git a/TD4/src/main/java/fr/miage23/filestore/api/dto/NodeDto.java b/TD4/src/main/java/fr/miage23/filestore/api/dto/NodeDto.java index 11f9af7079b1b5692c681df622de07d687fab2df..43c5c73193f6006698e6e173bf0d6e03b37f3d0d 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/dto/NodeDto.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/dto/NodeDto.java @@ -10,12 +10,13 @@ public class NodeDto { @FormParam("name") @PartType(MediaType.TEXT_PLAIN) - private String name; + private String name = null; @FormParam("data") @PartType(MediaType.APPLICATION_OCTET_STREAM) private InputStream data = null; public NodeDto() { + // Default constructor needed for JAX-RS } public String getName() { diff --git a/TD4/src/main/java/fr/miage23/filestore/api/exceptions/IllegalArgumentExceptionMapper.java b/TD4/src/main/java/fr/miage23/filestore/api/exceptions/IllegalArgumentExceptionMapper.java index d9ded4850e16f79057578eea9528d5b48cea5cc7..4e5e9ce58f0875a331e9af2ab7c8bda82ec023b6 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/exceptions/IllegalArgumentExceptionMapper.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/exceptions/IllegalArgumentExceptionMapper.java @@ -1,2 +1,13 @@ -package fr.miage23.filestore.api.exceptions;public class IllegalArgumentExceptionMapper { +package fr.miage23.filestore.api.exceptions; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> { + + @Override + public Response toResponse(IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build(); + } } 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 c7d43f9296b71f018e397c066b8b53af2a71771c..aed8f5a9b9442f4da3b0c1763eccc5e3990df3c7 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,6 +1,11 @@ package fr.miage23.filestore.api.resources; -import fr.miage23.filestore.api.dto.CreateNodeDto; +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.api.dto.NodeDto; import fr.miage23.filestore.files.FileService; import fr.miage23.filestore.files.InMemoryFileServiceBean; import fr.miage23.filestore.files.entity.Node; @@ -8,6 +13,7 @@ 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 jakarta.validation.Valid; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -39,8 +45,15 @@ public class NodesResource { */ @GET public Response redirectToDefaultNode() { - String defaultNodeUrl = uriInfo.getBaseUriBuilder().path("nodes/root").build().toString(); - return Response.seeOther(URI.create(defaultNodeUrl)).build(); + LOGGER.log(Level.INFO, "GET /api/nodes/root"); + try { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .create(); + return Response.ok(gson.toJson(service.get("root"), Node.class), MediaType.APPLICATION_JSON).build(); + } catch (NodeNotFoundException e) { + return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); + } } /** @@ -48,84 +61,107 @@ public class NodesResource { * * @param id The ID of the node to retrieve. * @return The Node object representing the requested node. - * @throws NodeNotFoundException If the specified node is not found. */ @GET @Path("{id}") @Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_JSON}) - public Node getNode(@PathParam("id") String id) throws NodeNotFoundException { + public Response getNode(@PathParam("id") String id) { LOGGER.log(Level.INFO, "GET /api/nodes/" + id); - return service.get(id); + try{ + Gson gson = new GsonBuilder() + .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .create(); + return Response.ok(gson.toJson(service.get(id), Node.class), MediaType.APPLICATION_JSON).build(); + } catch (NodeNotFoundException e) { + return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); + } } /** * Creates a new node with the provided parameters. * - * @param id The ID of the new node. - * @param name The name of the new node. - * @param uriInfo The UriInfo object to access URI information. - * @return Response object indicating the success of the operation. - * @throws NodeNotFoundException If the specified parent node is not found. - * @throws NodeTypeException If there is an issue with the node type. - * @throws NodeAlreadyExistsException If a node with the specified ID already exists. - */ - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response createNode(@FormParam("id") String id, @FormParam("name") String name, @Context UriInfo uriInfo) - throws NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException { - LOGGER.log(Level.INFO, "POST /api/nodes/" + id); - String newid = service.add(id, name); - return Response.created(uriInfo.getBaseUriBuilder().path(NodesResource.class).path(newid).build()).entity(newid).build(); - } - - /** - * Creates a new node with the provided parameters and a specified parent node ID. - * - * @param id The ID of the new node. - * @param name The name of the new node. - * @param uriInfo The UriInfo object to access URI information. - * @return Response object indicating the success of the operation. - * @throws NodeNotFoundException If the specified parent node is not found. - * @throws NodeTypeException If there is an issue with the node type. - * @throws NodeAlreadyExistsException If a node with the specified ID already exists. + * @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 createNodeWithPath(@PathParam("id") String id, @FormParam("name") String name, @Context UriInfo uriInfo) - throws NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException { + public Response createNode(@PathParam("id") String id, @FormParam("name") String name, @Context UriInfo info) { LOGGER.log(Level.INFO, "POST /api/nodes/" + id); - String newid = service.add(id, name); - return Response.created(uriInfo.getBaseUriBuilder().path(NodesResource.class).path(newid).build()).entity(newid).build(); + try { + String newid; + if (name != null) { + newid = service.add(id, name); + } else { + throw new IllegalArgumentException("A name must be provided"); + } + Gson gson = new GsonBuilder() + .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .create(); + return Response.ok(gson.toJson(service.get(newid), Node.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(); + } catch (NodeNotFoundException e) { + return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); + } catch (NodeTypeException | NodeAlreadyExistsException e) { + // Handle other exceptions as needed, possibly transforming them into different HTTP responses + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An unexpected error occurred.").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 containing information about 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 of the operation. - * @throws NodeNotFoundException If the specified parent node is not found. - * @throws NodeTypeException If there is an issue with the node type. - * @throws NodeAlreadyExistsException If a node with the specified ID already exists. - * @throws ContentException If there is an issue with the content. - * @throws IOException If an I/O error occurs. + * @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 CreateNodeDto dto, @Context UriInfo info) - throws NodeNotFoundException, NodeTypeException, NodeAlreadyExistsException, ContentException, IOException { + public Response createView(@PathParam("id") final String id, @MultipartForm @Valid NodeDto dto, @Context UriInfo info) { LOGGER.log(Level.INFO, "POST /api/nodes/" + id + " (html)"); - if (dto.getData() != null) { - service.add(id, dto.getName(), dto.getData()); - } else { - service.add(id, dto.getName()); + try { + 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()); + } else { + throw new IllegalArgumentException("A name must be provided"); + } + Gson gson = new GsonBuilder() + .registerTypeAdapter(Node.Type.class, new EnumTypeAdapter()) + .create(); + return Response.ok(gson.toJson(service.get(newid), Node.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(); + } catch (NodeNotFoundException e) { + return Response.status(Response.Status.NOT_FOUND).entity("Node not found.").build(); + } catch (NodeTypeException | NodeAlreadyExistsException | ContentException e) { + // Handle other exceptions as needed, possibly transforming them into different HTTP responses + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An unexpected error occurred.").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 Node.Type + private static class EnumTypeAdapter extends TypeAdapter<Node.Type> { + @Override + public void write(JsonWriter out, Node.Type value) throws IOException { + out.value(value.name()); + } + + @Override + public Node.Type read(JsonReader in) throws IOException { + return Node.Type.valueOf(in.nextString()); + } + } + } diff --git a/TD4/src/main/java/fr/miage23/filestore/api/template/TemplateBodyWriter.java b/TD4/src/main/java/fr/miage23/filestore/api/template/TemplateBodyWriter.java index 4d57223e8b1be369334c7725761ff48a7b78e249..1cb7795985315a374c33e75ecfcda5b53a38f98d 100644 --- a/TD4/src/main/java/fr/miage23/filestore/api/template/TemplateBodyWriter.java +++ b/TD4/src/main/java/fr/miage23/filestore/api/template/TemplateBodyWriter.java @@ -21,21 +21,45 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; +/** + * TemplateBodyWriter is a JAX-RS MessageBodyWriter responsible for processing instances of TemplateContent + * and rendering them as HTML using FreeMarker templates. + * + * The class is annotated with @Provider to indicate that it provides a service component for JAX-RS. + * It handles the production of HTML content with the specified media type TEXT_HTML. + * + * The TemplateBodyWriter utilizes FreeMarker for template processing. It loads templates from the "templates" + * directory using the class loader and renders them using the FreeMarker engine. + */ @Provider @Produces(MediaType.TEXT_HTML) public class TemplateBodyWriter implements MessageBodyWriter<TemplateContent> { private static final Logger LOGGER = Logger.getLogger(TemplateBodyWriter.class.getName()); + + // FreeMarker configuration for template processing private static final Configuration cfg = new Configuration(Configuration.VERSION_2_3_28); + + // Initialization block for configuring FreeMarker { cfg.setClassLoaderForTemplateLoading(TemplateBodyWriter.class.getClassLoader(), "templates"); cfg.setDefaultEncoding("UTF-8"); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); } + /** + * Checks if the given type is writable by this MessageBodyWriter. + * + * @param type the class type + * @param genericType the generic type + * @param annotations array of annotations associated with the message body writer + * @param mediaType the media type of the data that will be written + * @return true if the type is writable, false otherwise + */ @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { - if ( annotations != null ) { + if (annotations != null) { + // Check if the Template annotation is present if (Arrays.stream(annotations).anyMatch(a -> a.annotationType().equals(Template.class))) { LOGGER.log(Level.INFO, "Method contains template annotation"); return true; @@ -44,29 +68,61 @@ public class TemplateBodyWriter implements MessageBodyWriter<TemplateContent> { return false; } + /** + * Get the size of the entity. + * + * @param model the instance to write + * @param type the class type + * @param genericType the generic type + * @param annotations array of annotations associated with the message body writer + * @param mediaType the media type of the data that will be written + * @return -1 to indicate that the size is unknown or not applicable + */ @Override public long getSize(TemplateContent model, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } + /** + * Write a type to an HTTP response. + * + * @param model the instance to write + * @param type the class type + * @param genericType the generic type + * @param annotations array of annotations associated with the message body writer + * @param mediaType the media type of the data that will be written + * @param httpHeaders a map of the response headers + * @param entityStream the OutputStream for the response entity + * @throws IOException if an I/O exception occurs + * @throws WebApplicationException if a specific HTTP error response needs to be produced + */ @Override - public void writeTo(TemplateContent model, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { + public void writeTo(TemplateContent model, Class<?> type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { LOGGER.log(Level.INFO, "Template writing"); try { - Optional<Annotation> annotation = Arrays.stream(annotations).filter(a -> a.annotationType().equals(Template.class)).findFirst(); + // Find the Template annotation + Optional<Annotation> annotation = Arrays.stream(annotations) + .filter(a -> a.annotationType().equals(Template.class)).findFirst(); + if (annotation.isPresent()) { String name = ((Template) annotation.get()).name(); LOGGER.log(Level.FINE, "Applying template: " + name); LOGGER.log(Level.FINE, "using model: " + model); + + // Process the FreeMarker template freemarker.template.Template temp = cfg.getTemplate(name + ".ftl", Locale.getDefault()); StringWriter out = new StringWriter(); temp.process(model, out); + + // Write the processed template to the output stream entityStream.write(out.toString().getBytes()); } else { throw new WebApplicationException("Template annotation not found."); } - } catch ( TemplateException e ) { + } catch (TemplateException e) { throw new WebApplicationException(e); } } -} +} \ No newline at end of file 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 e9929a80a6db15e965d7a88947d86fc24a6b5be5..066d1f1cf4225f01dda9a955f82de5ffba93f97b 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 @@ -149,22 +149,6 @@ 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/resources/ValidationMessages.properties b/TD4/src/main/resources/ValidationMessages.properties index 0c05216b6eeacdd6ff6542d70ab795d3c3427afd..080da2de5e48fed2c6da5b1839bdf3fcbdda73c7 100644 --- a/TD4/src/main/resources/ValidationMessages.properties +++ b/TD4/src/main/resources/ValidationMessages.properties @@ -1 +1,7 @@ -invalid.filename=The filename contains forbidden characters \ No newline at end of file +invalid.filename_FR=Le nom du fichier contient des caract�res interdits +invalid.filename_EN=The filename contains forbidden characters +invalid.filename_ES=El nombre del archivo contiene caracteres no permitidos +invalid.filename_DE=Der Dateiname enth�lt unzul�ssige Zeichen +invalid.filename_IT=Il nome del file contiene caratteri non consentiti +invalid.filename_NL=De bestandsnaam bevat verboden tekens +invalid.filename_PT=O nome do arquivo cont�m caracteres proibidos