From 64ec0f63cae16478a933ef7d454fe1d28c8fa18a Mon Sep 17 00:00:00 2001 From: Julien Kirch Date: Sun, 20 Mar 2022 11:36:28 +0100 Subject: [PATCH] Add parameter to return the files when searching for a document (#582) --- .../com/sismics/docs/core/dao/FileDao.java | 23 +++++-- .../java/com/sismics/rest/util/RestUtil.java | 40 +++++++++++++ .../docs/rest/resource/DocumentResource.java | 60 +++++++++++++++++-- .../docs/rest/resource/FileResource.java | 23 ++----- .../docs/rest/TestDocumentResource.java | 42 +++++++++++-- 5 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java index 2f8ea730..fb2b3e4b 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java @@ -8,6 +8,8 @@ import com.sismics.util.context.ThreadLocalContext; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; +import javax.persistence.TypedQuery; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.UUID; @@ -172,22 +174,33 @@ public class FileDao { } /** - * Get files by document ID or all orphan files of an user. + * Get files by document ID or all orphan files of a user. * * @param userId User ID * @param documentId Document ID * @return List of files */ - @SuppressWarnings("unchecked") public List getByDocumentId(String userId, String documentId) { EntityManager em = ThreadLocalContext.get().getEntityManager(); if (documentId == null) { - Query q = em.createQuery("select f from File f where f.documentId is null and f.deleteDate is null and f.latestVersion = true and f.userId = :userId order by f.createDate asc"); + TypedQuery q = em.createQuery("select f from File f where f.documentId is null and f.deleteDate is null and f.latestVersion = true and f.userId = :userId order by f.createDate asc", File.class); q.setParameter("userId", userId); return q.getResultList(); + } else { + return getByDocumentsIds(Collections.singleton(documentId)); } - Query q = em.createQuery("select f from File f where f.documentId = :documentId and f.latestVersion = true and f.deleteDate is null order by f.order asc"); - q.setParameter("documentId", documentId); + } + + /** + * Get files by documents IDs. + * + * @param documentIds Documents IDs + * @return List of files + */ + public List getByDocumentsIds(Iterable documentIds) { + EntityManager em = ThreadLocalContext.get().getEntityManager(); + TypedQuery q = em.createQuery("select f from File f where f.documentId in :documentIds and f.latestVersion = true and f.deleteDate is null order by f.order asc", File.class); + q.setParameter("documentIds", documentIds); return q.getResultList(); } diff --git a/docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java b/docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java new file mode 100644 index 00000000..93c0e48c --- /dev/null +++ b/docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java @@ -0,0 +1,40 @@ +package com.sismics.rest.util; + +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.util.DirectoryUtil; +import com.sismics.docs.core.util.FileUtil; +import com.sismics.rest.exception.ServerException; +import com.sismics.util.JsonUtil; + +import javax.json.Json; +import javax.json.JsonObjectBuilder; +import java.io.IOException; +import java.nio.file.Files; + +/** + * Rest utilities. + * + * @author bgamard + */ +public class RestUtil { + /** + * Transform a File into its JSON representation + * @param fileDb a file + * @return the JSON + */ + public static JsonObjectBuilder fileToJsonObjectBuilder(File fileDb) { + try { + return Json.createObjectBuilder() + .add("id", fileDb.getId()) + .add("processing", FileUtil.isProcessingFile(fileDb.getId())) + .add("name", JsonUtil.nullable(fileDb.getName())) + .add("version", fileDb.getVersion()) + .add("mimetype", fileDb.getMimeType()) + .add("document_id", JsonUtil.nullable(fileDb.getDocumentId())) + .add("create_date", fileDb.getCreateDate().getTime()) + .add("size", Files.size(DirectoryUtil.getStorageDirectory().resolve(fileDb.getId()))); + } catch (IOException e) { + throw new ServerException("FileError", "Unable to get the size of " + fileDb.getId(), e); + } + } +} diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index 503f3f41..f8b586c7 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -27,11 +27,13 @@ import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.exception.ServerException; import com.sismics.rest.util.AclUtil; +import com.sismics.rest.util.RestUtil; import com.sismics.rest.util.ValidationUtil; import com.sismics.util.EmailUtil; import com.sismics.util.JsonUtil; import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.mime.MimeType; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataParam; @@ -73,6 +75,7 @@ public class DocumentResource extends BaseResource { * @apiGroup Document * @apiParam {String} id Document ID * @apiParam {String} share Share ID + * @apiParam {Booleans} files If true includes files information * @apiSuccess {String} id ID * @apiSuccess {String} title Title * @apiSuccess {String} description Description @@ -119,6 +122,12 @@ public class DocumentResource extends BaseResource { * @apiSuccess {String} route_step.name Route step name * @apiSuccess {String="APPROVE", "VALIDATE"} route_step.type Route step type * @apiSuccess {Boolean} route_step.transitionable True if the route step is actionable by the current user + * @apiSuccess {Object[]} files List of files + * @apiSuccess {String} files.id ID + * @apiSuccess {String} files.name File name + * @apiSuccess {String} files.version Zero-based version number + * @apiSuccess {String} files.mimetype MIME type + * @apiSuccess {String} files.create_date Create date (timestamp) * @apiError (client) NotFound Document not found * @apiPermission none * @apiVersion 1.5.0 @@ -131,7 +140,8 @@ public class DocumentResource extends BaseResource { @Path("{id: [a-z0-9\\-]+}") public Response get( @PathParam("id") String documentId, - @QueryParam("share") String shareId) { + @QueryParam("share") String shareId, + @QueryParam("files") Boolean files) { authenticate(); DocumentDao documentDao = new DocumentDao(); @@ -240,6 +250,19 @@ public class DocumentResource extends BaseResource { // Add custom metadata MetadataUtil.addMetadata(document, documentId); + // Add files + if (Boolean.TRUE == files) { + FileDao fileDao = new FileDao(); + List fileList = fileDao.getByDocumentsIds(Collections.singleton(documentId)); + + JsonArrayBuilder filesArrayBuilder = Json.createArrayBuilder(); + for (File fileDb : fileList) { + filesArrayBuilder.add(RestUtil.fileToJsonObjectBuilder(fileDb)); + } + + document.add("files", filesArrayBuilder); + } + return Response.ok().entity(document.build()).build(); } @@ -327,6 +350,7 @@ public class DocumentResource extends BaseResource { * @apiParam {Number} sort_column Column index to sort on * @apiParam {Boolean} asc If true, sort in ascending order * @apiParam {String} search Search query + * @apiParam {Booleans} files If true includes files information * @apiSuccess {Number} total Total number of documents * @apiSuccess {Object[]} documents List of documents * @apiSuccess {String} documents.id ID @@ -345,6 +369,12 @@ public class DocumentResource extends BaseResource { * @apiSuccess {String} documents.tags.id ID * @apiSuccess {String} documents.tags.name Name * @apiSuccess {String} documents.tags.color Color + * @apiSuccess {Object[]} documents.files List of files + * @apiSuccess {String} documents.files.id ID + * @apiSuccess {String} documents.files.name File name + * @apiSuccess {String} documents.files.version Zero-based version number + * @apiSuccess {String} documents.files.mimetype MIME type + * @apiSuccess {String} documents.files.create_date Create date (timestamp) * @apiSuccess {String[]} suggestions List of search suggestions * @apiError (client) ForbiddenError Access denied * @apiError (server) SearchError Error searching in documents @@ -356,6 +386,7 @@ public class DocumentResource extends BaseResource { * @param sortColumn Sort column * @param asc Sorting * @param search Search query + * @param files Files list * @return Response */ @GET @@ -365,7 +396,8 @@ public class DocumentResource extends BaseResource { @QueryParam("offset") Integer offset, @QueryParam("sort_column") Integer sortColumn, @QueryParam("asc") Boolean asc, - @QueryParam("search") String search) { + @QueryParam("search") String search, + @QueryParam("files") Boolean files) { if (!authenticate()) { throw new ForbiddenClientException(); } @@ -385,6 +417,14 @@ public class DocumentResource extends BaseResource { throw new ServerException("SearchError", "Error searching in documents", e); } + // Find the files of the documents + List filesList = null; + if (Boolean.TRUE == files) { + Iterable documentsIds = CollectionUtils.collect(paginatedList.getResultList(), DocumentDto::getId); + FileDao fileDao = new FileDao(); + filesList = fileDao.getByDocumentsIds(documentsIds); + } + for (DocumentDto documentDto : paginatedList.getResultList()) { // Get tags accessible by the current user on this document List tagDtoList = tagDao.findByCriteria(new TagCriteria() @@ -397,8 +437,8 @@ public class DocumentResource extends BaseResource { .add("name", tagDto.getName()) .add("color", tagDto.getColor())); } - - documents.add(Json.createObjectBuilder() + + JsonObjectBuilder documentObjectBuilder = Json.createObjectBuilder() .add("id", documentDto.getId()) .add("highlight", JsonUtil.nullable(documentDto.getHighlight())) .add("file_id", JsonUtil.nullable(documentDto.getFileId())) @@ -411,7 +451,17 @@ public class DocumentResource extends BaseResource { .add("active_route", documentDto.isActiveRoute()) .add("current_step_name", JsonUtil.nullable(documentDto.getCurrentStepName())) .add("file_count", documentDto.getFileCount()) - .add("tags", tags)); + .add("tags", tags); + if (Boolean.TRUE == files) { + JsonArrayBuilder filesArrayBuilder = Json.createArrayBuilder(); + // Find files matching the document + Collection filesOfDocument = CollectionUtils.select(filesList, file -> file.getDocumentId().equals(documentDto.getId())); + for (File fileDb : filesOfDocument) { + filesArrayBuilder.add(RestUtil.fileToJsonObjectBuilder(fileDb)); + } + documentObjectBuilder.add("files", filesArrayBuilder); + } + documents.add(documentObjectBuilder); } JsonArrayBuilder suggestions = Json.createArrayBuilder(); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index 18d43d83..9c14648e 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -21,6 +21,7 @@ import com.sismics.docs.core.util.FileUtil; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.exception.ServerException; +import com.sismics.rest.util.RestUtil; import com.sismics.rest.util.ValidationUtil; import com.sismics.util.HttpUtil; import com.sismics.util.JsonUtil; @@ -425,29 +426,15 @@ public class FileResource extends BaseResource { } else if (!authenticated) { throw new ForbiddenClientException(); } - - FileDao fileDao = new FileDao(); - List fileList = fileDao.getByDocumentId(principal.getId(), documentId); + FileDao fileDao = new FileDao(); JsonArrayBuilder files = Json.createArrayBuilder(); - for (File fileDb : fileList) { - try { - files.add(Json.createObjectBuilder() - .add("id", fileDb.getId()) - .add("processing", FileUtil.isProcessingFile(fileDb.getId())) - .add("name", JsonUtil.nullable(fileDb.getName())) - .add("version", fileDb.getVersion()) - .add("mimetype", fileDb.getMimeType()) - .add("document_id", JsonUtil.nullable(fileDb.getDocumentId())) - .add("create_date", fileDb.getCreateDate().getTime()) - .add("size", Files.size(DirectoryUtil.getStorageDirectory().resolve(fileDb.getId())))); - } catch (IOException e) { - throw new ServerException("FileError", "Unable to get the size of " + fileDb.getId(), e); - } + for (File fileDb : fileDao.getByDocumentId(principal.getId(), documentId)) { + files.add(RestUtil.fileToJsonObjectBuilder(fileDb)); } - JsonObjectBuilder response = Json.createObjectBuilder() .add("files", files); + return Response.ok().entity(response.build()).build(); } diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java index 0c16822a..13468a0e 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java @@ -264,7 +264,8 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertEquals(document2Id, relations.getJsonObject(0).getString("id")); Assert.assertFalse(relations.getJsonObject(0).getBoolean("source")); Assert.assertEquals("My super title document 2", relations.getJsonObject(0).getString("title")); - + Assert.assertFalse(json.containsKey("files")); + // Get document 2 json = target().path("/document/" + document2Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token) @@ -275,7 +276,8 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertEquals(document1Id, relations.getJsonObject(0).getString("id")); Assert.assertTrue(relations.getJsonObject(0).getBoolean("source")); Assert.assertEquals("My super title document 1", relations.getJsonObject(0).getString("title")); - + Assert.assertFalse(json.containsKey("files")); + // Create a tag json = target().path("/tag").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token) @@ -330,7 +332,26 @@ public class TestDocumentResource extends BaseJerseyTest { .get(JsonObject.class); documents = json.getJsonArray("documents"); Assert.assertEquals(1, documents.size()); - + Assert.assertEquals(document1Id, documents.getJsonObject(0).getString("id")); + Assert.assertFalse(documents.getJsonObject(0).containsKey("files")); + + // Search documents by query with files + json = target().path("/document/list") + .queryParam("files", true) + .queryParam("search", "new") + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token) + .get(JsonObject.class); + documents = json.getJsonArray("documents"); + Assert.assertEquals(1, documents.size()); + Assert.assertEquals(1, documents.size()); + Assert.assertEquals(document1Id, documents.getJsonObject(0).getString("id")); + JsonArray files = documents.getJsonObject(0).getJsonArray("files"); + Assert.assertEquals(1, files.size()); + Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id")); + Assert.assertEquals("Einstein-Roosevelt-letter.png", files.getJsonObject(0).getString("name")); + Assert.assertEquals("image/png", files.getJsonObject(0).getString("mimetype")); + // Get document 1 json = target().path("/document/" + document1Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token) @@ -353,7 +374,20 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertEquals("document1", contributors.getJsonObject(0).getString("username")); relations = json.getJsonArray("relations"); Assert.assertEquals(0, relations.size()); - + Assert.assertFalse(json.containsKey("files")); + + // Get document 1 with its files + json = target().path("/document/" + document1Id) + .queryParam("files", true) + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token) + .get(JsonObject.class); + files = json.getJsonArray("files"); + Assert.assertEquals(1, files.size()); + Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id")); + Assert.assertEquals("Einstein-Roosevelt-letter.png", files.getJsonObject(0).getString("name")); + Assert.assertEquals("image/png", files.getJsonObject(0).getString("mimetype")); + // Get document 2 json = target().path("/document/" + document1Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token)