From 3902d6361ea0ef02d56cfff4069a3ae19a72b36e Mon Sep 17 00:00:00 2001 From: Benjamin Gamard Date: Fri, 23 Nov 2018 14:54:11 +0100 Subject: [PATCH] #256: versioning API --- .../com/sismics/docs/core/dao/FileDao.java | 8 ++-- .../com/sismics/docs/core/model/jpa/File.java | 45 +++++++++++++++++++ .../docs/core/service/InboxService.java | 4 +- .../com/sismics/docs/core/util/FileUtil.java | 45 ++++++++++++++----- .../src/main/resources/config.properties | 2 +- .../resources/db/update/dbupdate-022-0.sql | 5 +++ docs-web/src/dev/resources/config.properties | 2 +- .../docs/rest/resource/DocumentResource.java | 4 +- .../docs/rest/resource/FileResource.java | 6 ++- docs-web/src/prod/resources/config.properties | 2 +- .../src/stress/resources/config.properties | 2 +- .../sismics/docs/rest/TestFileResource.java | 34 ++++++++++++++ 12 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 docs-core/src/main/resources/db/update/dbupdate-022-0.sql 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 4610621e..a35a03e4 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 @@ -148,7 +148,9 @@ public class FileDao { fileDb.setContent(file.getContent()); fileDb.setOrder(file.getOrder()); fileDb.setMimeType(file.getMimeType()); - + fileDb.setVersionId(file.getVersionId()); + fileDb.setLatestVersion(file.isLatestVersion()); + return file; } @@ -180,11 +182,11 @@ public class FileDao { 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.userId = :userId order by f.createDate asc"); + 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"); q.setParameter("userId", userId); return q.getResultList(); } - Query q = em.createQuery("select f from File f where f.documentId = :documentId and f.deleteDate is null order by f.order asc"); + 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); return q.getResultList(); } diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java index 3db2ab7b..c3e7064a 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java @@ -71,6 +71,24 @@ public class File implements Loggable { @Column(name = "FIL_ORDER_N") private Integer order; + /** + * Version ID. + */ + @Column(name = "FIL_IDVERSION_C") + private String versionId; + + /** + * Version number (starting at 0). + */ + @Column(name = "FIL_VERSION_N", nullable = false) + private Integer version; + + /** + * True if it's the latest version of the file. + */ + @Column(name = "FIL_LATESTVERSION_B", nullable = false) + private boolean latestVersion; + /** * Private key to decrypt the file. * Not saved to database, of course. @@ -160,6 +178,33 @@ public class File implements Loggable { this.privateKey = privateKey; } + public String getVersionId() { + return versionId; + } + + public File setVersionId(String versionId) { + this.versionId = versionId; + return this; + } + + public Integer getVersion() { + return version; + } + + public File setVersion(Integer version) { + this.version = version; + return this; + } + + public boolean isLatestVersion() { + return latestVersion; + } + + public File setLatestVersion(boolean latestVersion) { + this.latestVersion = latestVersion; + return this; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java b/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java index a349d329..ae0b7500 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java +++ b/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java @@ -212,7 +212,7 @@ public class InboxService extends AbstractScheduledService { } // Save the document, create the base ACLs - document = DocumentUtil.createDocument(document, "admin"); + DocumentUtil.createDocument(document, "admin"); // Add the tag String tagId = ConfigUtil.getConfigStringValue(ConfigType.INBOX_TAG); @@ -232,7 +232,7 @@ public class InboxService extends AbstractScheduledService { // Add files to the document for (EmailUtil.FileContent fileContent : mailContent.getFileContentList()) { - FileUtil.createFile(fileContent.getName(), fileContent.getFile(), fileContent.getSize(), + FileUtil.createFile(fileContent.getName(), null, fileContent.getFile(), fileContent.getSize(), document.getLanguage(), "admin", document.getId()); } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java index a4604038..98dbeca0 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java @@ -98,6 +98,7 @@ public class FileUtil { * Create a new file. * * @param name File name, can be null + * @param previousFileId ID of the previous version of the file, if the new file is a new version * @param unencryptedFile Path to the unencrypted file * @param fileSize File size * @param language File language, can be null if associated to no document @@ -106,7 +107,7 @@ public class FileUtil { * @return File ID * @throws Exception e */ - public static String createFile(String name, Path unencryptedFile, long fileSize, String language, String userId, String documentId) throws Exception { + public static String createFile(String name, String previousFileId, Path unencryptedFile, long fileSize, String language, String userId, String documentId) throws Exception { // Validate mime type String mimeType; try { @@ -132,22 +133,42 @@ public class FileUtil { } } - // Get files of this document - FileDao fileDao = new FileDao(); - int order = 0; - if (documentId != null) { - for (File file : fileDao.getByDocumentId(userId, documentId)) { - file.setOrder(order++); - } - } - - // Create the file + // Prepare the file File file = new File(); - file.setOrder(order); + file.setOrder(0); + file.setVersion(0); + file.setLatestVersion(true); file.setDocumentId(documentId); file.setName(StringUtils.abbreviate(name, 200)); file.setMimeType(mimeType); file.setUserId(userId); + + // Get files of this document + FileDao fileDao = new FileDao(); + if (documentId != null) { + if (previousFileId == null) { + // It's not a new version, so put it in last order + file.setOrder(fileDao.getByDocumentId(userId, documentId).size()); + } else { + // It's a new version, update the previous version + File previousFile = fileDao.getActiveById(previousFileId); + if (previousFile == null || !previousFile.getDocumentId().equals(documentId)) { + throw new IOException("Previous version mismatch"); + } + + if (previousFile.getVersionId() == null) { + previousFile.setVersionId(UUID.randomUUID().toString()); + } + + previousFile.setLatestVersion(false); + file.setVersionId(previousFile.getVersionId()); + file.setVersion(previousFile.getVersion() + 1); + + fileDao.update(previousFile); + } + } + + // Create the file String fileId = fileDao.create(file, userId); // Save the file diff --git a/docs-core/src/main/resources/config.properties b/docs-core/src/main/resources/config.properties index eb3e256a..5d8d3532 100644 --- a/docs-core/src/main/resources/config.properties +++ b/docs-core/src/main/resources/config.properties @@ -1 +1 @@ -db.version=21 \ No newline at end of file +db.version=22 \ No newline at end of file diff --git a/docs-core/src/main/resources/db/update/dbupdate-022-0.sql b/docs-core/src/main/resources/db/update/dbupdate-022-0.sql new file mode 100644 index 00000000..c8333f93 --- /dev/null +++ b/docs-core/src/main/resources/db/update/dbupdate-022-0.sql @@ -0,0 +1,5 @@ +alter table T_FILE add column FIL_VERSION_N int not null default 0; +alter table T_FILE add column FIL_LATESTVERSION_B bit not null default 1; +alter table T_FILE add column FIL_IDVERSION_C varchar(36); + +update T_CONFIG set CFG_VALUE_C = '22' where CFG_ID_C = 'DB_VERSION'; diff --git a/docs-web/src/dev/resources/config.properties b/docs-web/src/dev/resources/config.properties index e55e3dbc..7ab7347c 100644 --- a/docs-web/src/dev/resources/config.properties +++ b/docs-web/src/dev/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=21 \ No newline at end of file +db.version=22 \ No newline at end of file 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 2c7380b7..7de9134f 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 @@ -900,7 +900,7 @@ public class DocumentResource extends BaseResource { } // Save the document, create the base ACLs - document = DocumentUtil.createDocument(document, principal.getId()); + DocumentUtil.createDocument(document, principal.getId()); // Raise a document created event DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent(); @@ -911,7 +911,7 @@ public class DocumentResource extends BaseResource { // Add files to the document try { for (EmailUtil.FileContent fileContent : mailContent.getFileContentList()) { - FileUtil.createFile(fileContent.getName(), fileContent.getFile(), fileContent.getSize(), + FileUtil.createFile(fileContent.getName(), null, fileContent.getFile(), fileContent.getSize(), document.getLanguage(), principal.getId(), document.getId()); } } catch (IOException e) { 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 4f03f156..24fd97bb 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 @@ -66,6 +66,7 @@ public class FileResource extends BaseResource { * @apiName PutFile * @apiGroup File * @apiParam {String} id Document ID + * @apiParam {String} previousFileId ID of the file to replace by this new version * @apiParam {String} file File data * @apiSuccess {String} status Status OK * @apiSuccess {String} id File ID @@ -88,6 +89,7 @@ public class FileResource extends BaseResource { @Consumes("multipart/form-data") public Response add( @FormDataParam("id") String documentId, + @FormDataParam("previousFileId") String previousFileId, @FormDataParam("file") FormDataBodyPart fileBodyPart) { if (!authenticate()) { throw new ForbiddenClientException(); @@ -122,7 +124,7 @@ public class FileResource extends BaseResource { try { String name = fileBodyPart.getContentDisposition() != null ? URLDecoder.decode(fileBodyPart.getContentDisposition().getFileName(), "UTF-8") : null; - String fileId = FileUtil.createFile(name, unencryptedFile, fileSize, documentDto == null ? + String fileId = FileUtil.createFile(name, previousFileId, unencryptedFile, fileSize, documentDto == null ? null : documentDto.getLanguage(), principal.getId(), documentId); // Always return OK @@ -392,6 +394,7 @@ public class FileResource extends BaseResource { * @apiSuccess {String} files.id ID * @apiSuccess {String} files.mimetype MIME type * @apiSuccess {String} files.name File name + * @apiSuccess {String} files.version Zero-based version number * @apiSuccess {String} files.processing True if the file is currently processing * @apiSuccess {String} files.document_id Document ID * @apiSuccess {String} files.create_date Create date (timestamp) @@ -433,6 +436,7 @@ public class FileResource extends BaseResource { .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()) diff --git a/docs-web/src/prod/resources/config.properties b/docs-web/src/prod/resources/config.properties index e55e3dbc..7ab7347c 100644 --- a/docs-web/src/prod/resources/config.properties +++ b/docs-web/src/prod/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=21 \ No newline at end of file +db.version=22 \ No newline at end of file diff --git a/docs-web/src/stress/resources/config.properties b/docs-web/src/stress/resources/config.properties index e55e3dbc..7ab7347c 100644 --- a/docs-web/src/stress/resources/config.properties +++ b/docs-web/src/stress/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=21 \ No newline at end of file +db.version=22 \ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java index 053b72e6..278f1bb6 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java @@ -140,9 +140,11 @@ public class TestFileResource extends BaseJerseyTest { Assert.assertEquals(2, files.size()); Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id")); Assert.assertEquals("PIA00452.jpg", files.getJsonObject(0).getString("name")); + Assert.assertEquals(0, files.getJsonObject(0).getInt("version")); Assert.assertEquals(163510L, files.getJsonObject(0).getJsonNumber("size").longValue()); Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id")); Assert.assertEquals("PIA00452.jpg", files.getJsonObject(1).getString("name")); + Assert.assertEquals(0, files.getJsonObject(1).getInt("version")); // Rename a file target().path("file/" + file1Id) @@ -225,6 +227,38 @@ public class TestFileResource extends BaseJerseyTest { target().path("/file/" + file2Id + "/process").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token) .post(Entity.form(new Form()), JsonObject.class); + + // Add a new version to a file + String file3Id; + try (InputStream is0 = Resources.getResource("file/document.txt").openStream()) { + StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is0, "document.txt"); + try (FormDataMultiPart multiPart = new FormDataMultiPart()) { + json = target() + .register(MultiPartFeature.class) + .path("/file").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token) + .put(Entity.entity( + multiPart + .field("id", document1Id) + .field("previousFileId", file2Id) + .bodyPart(streamDataBodyPart), + MediaType.MULTIPART_FORM_DATA_TYPE), JsonObject.class); + file3Id = json.getString("id"); + Assert.assertNotNull(file2Id); + } + } + + // Check the newly created version + json = target().path("/file/list") + .queryParam("id", document1Id) + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token) + .get(JsonObject.class); + files = json.getJsonArray("files"); + Assert.assertEquals(1, files.size()); + Assert.assertEquals(file3Id, files.getJsonObject(0).getString("id")); + Assert.assertEquals("document.txt", files.getJsonObject(0).getString("name")); + Assert.assertEquals(1, files.getJsonObject(0).getInt("version")); } /**