#256: versioning API

This commit is contained in:
Benjamin Gamard 2018-11-23 14:54:11 +01:00
parent d8d5249a23
commit 3902d6361e
12 changed files with 135 additions and 24 deletions

View File

@ -148,6 +148,8 @@ public class FileDao {
fileDb.setContent(file.getContent()); fileDb.setContent(file.getContent());
fileDb.setOrder(file.getOrder()); fileDb.setOrder(file.getOrder());
fileDb.setMimeType(file.getMimeType()); fileDb.setMimeType(file.getMimeType());
fileDb.setVersionId(file.getVersionId());
fileDb.setLatestVersion(file.isLatestVersion());
return file; return file;
} }
@ -180,11 +182,11 @@ public class FileDao {
public List<File> getByDocumentId(String userId, String documentId) { public List<File> getByDocumentId(String userId, String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager(); EntityManager em = ThreadLocalContext.get().getEntityManager();
if (documentId == null) { 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); q.setParameter("userId", userId);
return q.getResultList(); 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); q.setParameter("documentId", documentId);
return q.getResultList(); return q.getResultList();
} }

View File

@ -71,6 +71,24 @@ public class File implements Loggable {
@Column(name = "FIL_ORDER_N") @Column(name = "FIL_ORDER_N")
private Integer order; 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. * Private key to decrypt the file.
* Not saved to database, of course. * Not saved to database, of course.
@ -160,6 +178,33 @@ public class File implements Loggable {
this.privateKey = privateKey; 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 @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)

View File

@ -212,7 +212,7 @@ public class InboxService extends AbstractScheduledService {
} }
// Save the document, create the base ACLs // Save the document, create the base ACLs
document = DocumentUtil.createDocument(document, "admin"); DocumentUtil.createDocument(document, "admin");
// Add the tag // Add the tag
String tagId = ConfigUtil.getConfigStringValue(ConfigType.INBOX_TAG); String tagId = ConfigUtil.getConfigStringValue(ConfigType.INBOX_TAG);
@ -232,7 +232,7 @@ public class InboxService extends AbstractScheduledService {
// Add files to the document // Add files to the document
for (EmailUtil.FileContent fileContent : mailContent.getFileContentList()) { 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()); document.getLanguage(), "admin", document.getId());
} }
} }

View File

@ -98,6 +98,7 @@ public class FileUtil {
* Create a new file. * Create a new file.
* *
* @param name File name, can be null * @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 unencryptedFile Path to the unencrypted file
* @param fileSize File size * @param fileSize File size
* @param language File language, can be null if associated to no document * @param language File language, can be null if associated to no document
@ -106,7 +107,7 @@ public class FileUtil {
* @return File ID * @return File ID
* @throws Exception e * @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 // Validate mime type
String mimeType; String mimeType;
try { try {
@ -132,22 +133,42 @@ public class FileUtil {
} }
} }
// Get files of this document // Prepare the file
FileDao fileDao = new FileDao();
int order = 0;
if (documentId != null) {
for (File file : fileDao.getByDocumentId(userId, documentId)) {
file.setOrder(order++);
}
}
// Create the file
File file = new File(); File file = new File();
file.setOrder(order); file.setOrder(0);
file.setVersion(0);
file.setLatestVersion(true);
file.setDocumentId(documentId); file.setDocumentId(documentId);
file.setName(StringUtils.abbreviate(name, 200)); file.setName(StringUtils.abbreviate(name, 200));
file.setMimeType(mimeType); file.setMimeType(mimeType);
file.setUserId(userId); 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); String fileId = fileDao.create(file, userId);
// Save the file // Save the file

View File

@ -1 +1 @@
db.version=21 db.version=22

View File

@ -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';

View File

@ -1,3 +1,3 @@
api.current_version=${project.version} api.current_version=${project.version}
api.min_version=1.0 api.min_version=1.0
db.version=21 db.version=22

View File

@ -900,7 +900,7 @@ public class DocumentResource extends BaseResource {
} }
// Save the document, create the base ACLs // Save the document, create the base ACLs
document = DocumentUtil.createDocument(document, principal.getId()); DocumentUtil.createDocument(document, principal.getId());
// Raise a document created event // Raise a document created event
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent(); DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
@ -911,7 +911,7 @@ public class DocumentResource extends BaseResource {
// Add files to the document // Add files to the document
try { try {
for (EmailUtil.FileContent fileContent : mailContent.getFileContentList()) { 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()); document.getLanguage(), principal.getId(), document.getId());
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -66,6 +66,7 @@ public class FileResource extends BaseResource {
* @apiName PutFile * @apiName PutFile
* @apiGroup File * @apiGroup File
* @apiParam {String} id Document ID * @apiParam {String} id Document ID
* @apiParam {String} previousFileId ID of the file to replace by this new version
* @apiParam {String} file File data * @apiParam {String} file File data
* @apiSuccess {String} status Status OK * @apiSuccess {String} status Status OK
* @apiSuccess {String} id File ID * @apiSuccess {String} id File ID
@ -88,6 +89,7 @@ public class FileResource extends BaseResource {
@Consumes("multipart/form-data") @Consumes("multipart/form-data")
public Response add( public Response add(
@FormDataParam("id") String documentId, @FormDataParam("id") String documentId,
@FormDataParam("previousFileId") String previousFileId,
@FormDataParam("file") FormDataBodyPart fileBodyPart) { @FormDataParam("file") FormDataBodyPart fileBodyPart) {
if (!authenticate()) { if (!authenticate()) {
throw new ForbiddenClientException(); throw new ForbiddenClientException();
@ -122,7 +124,7 @@ public class FileResource extends BaseResource {
try { try {
String name = fileBodyPart.getContentDisposition() != null ? String name = fileBodyPart.getContentDisposition() != null ?
URLDecoder.decode(fileBodyPart.getContentDisposition().getFileName(), "UTF-8") : 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); null : documentDto.getLanguage(), principal.getId(), documentId);
// Always return OK // Always return OK
@ -392,6 +394,7 @@ public class FileResource extends BaseResource {
* @apiSuccess {String} files.id ID * @apiSuccess {String} files.id ID
* @apiSuccess {String} files.mimetype MIME type * @apiSuccess {String} files.mimetype MIME type
* @apiSuccess {String} files.name File name * @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.processing True if the file is currently processing
* @apiSuccess {String} files.document_id Document ID * @apiSuccess {String} files.document_id Document ID
* @apiSuccess {String} files.create_date Create date (timestamp) * @apiSuccess {String} files.create_date Create date (timestamp)
@ -433,6 +436,7 @@ public class FileResource extends BaseResource {
.add("id", fileDb.getId()) .add("id", fileDb.getId())
.add("processing", FileUtil.isProcessingFile(fileDb.getId())) .add("processing", FileUtil.isProcessingFile(fileDb.getId()))
.add("name", JsonUtil.nullable(fileDb.getName())) .add("name", JsonUtil.nullable(fileDb.getName()))
.add("version", fileDb.getVersion())
.add("mimetype", fileDb.getMimeType()) .add("mimetype", fileDb.getMimeType())
.add("document_id", JsonUtil.nullable(fileDb.getDocumentId())) .add("document_id", JsonUtil.nullable(fileDb.getDocumentId()))
.add("create_date", fileDb.getCreateDate().getTime()) .add("create_date", fileDb.getCreateDate().getTime())

View File

@ -1,3 +1,3 @@
api.current_version=${project.version} api.current_version=${project.version}
api.min_version=1.0 api.min_version=1.0
db.version=21 db.version=22

View File

@ -1,3 +1,3 @@
api.current_version=${project.version} api.current_version=${project.version}
api.min_version=1.0 api.min_version=1.0
db.version=21 db.version=22

View File

@ -140,9 +140,11 @@ public class TestFileResource extends BaseJerseyTest {
Assert.assertEquals(2, files.size()); Assert.assertEquals(2, files.size());
Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id")); Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id"));
Assert.assertEquals("PIA00452.jpg", files.getJsonObject(0).getString("name")); 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(163510L, files.getJsonObject(0).getJsonNumber("size").longValue());
Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id")); Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id"));
Assert.assertEquals("PIA00452.jpg", files.getJsonObject(1).getString("name")); Assert.assertEquals("PIA00452.jpg", files.getJsonObject(1).getString("name"));
Assert.assertEquals(0, files.getJsonObject(1).getInt("version"));
// Rename a file // Rename a file
target().path("file/" + file1Id) target().path("file/" + file1Id)
@ -225,6 +227,38 @@ public class TestFileResource extends BaseJerseyTest {
target().path("/file/" + file2Id + "/process").request() target().path("/file/" + file2Id + "/process").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token)
.post(Entity.form(new Form()), JsonObject.class); .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"));
} }
/** /**