2013-07-27 18:33:20 +02:00
|
|
|
package com.sismics.docs.rest.resource;
|
|
|
|
|
2015-03-03 00:23:30 +01:00
|
|
|
import com.google.common.base.Strings;
|
2013-08-18 01:26:34 +02:00
|
|
|
import com.google.common.collect.Lists;
|
2013-08-20 21:51:07 +02:00
|
|
|
import com.google.common.io.ByteStreams;
|
2015-05-09 14:44:19 +02:00
|
|
|
import com.sismics.docs.core.constant.PermType;
|
2018-03-29 17:59:47 +02:00
|
|
|
import com.sismics.docs.core.dao.AclDao;
|
|
|
|
import com.sismics.docs.core.dao.DocumentDao;
|
|
|
|
import com.sismics.docs.core.dao.FileDao;
|
|
|
|
import com.sismics.docs.core.dao.UserDao;
|
|
|
|
import com.sismics.docs.core.dao.dto.DocumentDto;
|
2016-02-21 14:21:20 +01:00
|
|
|
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
|
2013-08-17 14:16:55 +02:00
|
|
|
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
2018-03-29 11:34:25 +02:00
|
|
|
import com.sismics.docs.core.event.FileUpdatedAsyncEvent;
|
2018-04-05 12:45:04 +02:00
|
|
|
import com.sismics.docs.core.model.context.AppContext;
|
2013-07-27 18:33:20 +02:00
|
|
|
import com.sismics.docs.core.model.jpa.File;
|
2013-08-20 21:51:07 +02:00
|
|
|
import com.sismics.docs.core.model.jpa.User;
|
2017-11-23 15:32:20 +01:00
|
|
|
import com.sismics.docs.core.util.DirectoryUtil;
|
|
|
|
import com.sismics.docs.core.util.EncryptionUtil;
|
|
|
|
import com.sismics.docs.core.util.FileUtil;
|
2013-07-27 18:33:20 +02:00
|
|
|
import com.sismics.rest.exception.ClientException;
|
|
|
|
import com.sismics.rest.exception.ForbiddenClientException;
|
|
|
|
import com.sismics.rest.exception.ServerException;
|
|
|
|
import com.sismics.rest.util.ValidationUtil;
|
2017-11-23 15:32:20 +01:00
|
|
|
import com.sismics.util.HttpUtil;
|
2018-02-02 12:37:56 +01:00
|
|
|
import com.sismics.util.JsonUtil;
|
2016-08-26 21:22:27 +02:00
|
|
|
import com.sismics.util.context.ThreadLocalContext;
|
2013-08-18 14:48:48 +02:00
|
|
|
import com.sismics.util.mime.MimeType;
|
2016-08-26 21:22:27 +02:00
|
|
|
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
|
|
|
|
import org.glassfish.jersey.media.multipart.FormDataParam;
|
|
|
|
|
|
|
|
import javax.json.Json;
|
|
|
|
import javax.json.JsonArrayBuilder;
|
|
|
|
import javax.json.JsonObjectBuilder;
|
|
|
|
import javax.ws.rs.*;
|
2017-11-23 15:32:20 +01:00
|
|
|
import javax.ws.rs.core.HttpHeaders;
|
2016-08-26 21:22:27 +02:00
|
|
|
import javax.ws.rs.core.MediaType;
|
|
|
|
import javax.ws.rs.core.Response;
|
|
|
|
import javax.ws.rs.core.Response.Status;
|
|
|
|
import javax.ws.rs.core.StreamingOutput;
|
2017-11-23 15:32:20 +01:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
2016-11-20 18:41:42 +01:00
|
|
|
import java.net.URISyntaxException;
|
2018-03-23 16:41:02 +01:00
|
|
|
import java.net.URLDecoder;
|
2016-08-26 21:22:27 +02:00
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Paths;
|
2017-11-06 16:45:47 +01:00
|
|
|
import java.nio.file.StandardCopyOption;
|
2016-08-26 21:22:27 +02:00
|
|
|
import java.text.MessageFormat;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.zip.ZipEntry;
|
|
|
|
import java.util.zip.ZipOutputStream;
|
2013-07-27 18:33:20 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* File REST resources.
|
|
|
|
*
|
|
|
|
* @author bgamard
|
|
|
|
*/
|
|
|
|
@Path("/file")
|
|
|
|
public class FileResource extends BaseResource {
|
|
|
|
/**
|
2015-03-03 00:23:30 +01:00
|
|
|
* Add a file (with or without a document).
|
2016-05-12 01:26:02 +02:00
|
|
|
*
|
|
|
|
* @api {put} /file Add a file
|
|
|
|
* @apiDescription A file can be added without associated document, and will go in a temporary storage waiting for one.
|
|
|
|
* This resource accepts only multipart/form-data.
|
|
|
|
* @apiName PutFile
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id Document ID
|
|
|
|
* @apiParam {String} file File data
|
|
|
|
* @apiSuccess {String} status Status OK
|
|
|
|
* @apiSuccess {String} id File ID
|
|
|
|
* @apiSuccess {Number} size File size (in bytes)
|
|
|
|
* @apiError (client) ForbiddenError Access denied
|
|
|
|
* @apiError (client) ValidationError Validation error
|
|
|
|
* @apiError (client) NotFound Document not found
|
|
|
|
* @apiError (server) StreamError Error reading the input file
|
|
|
|
* @apiError (server) ErrorGuessMime Error guessing mime type
|
|
|
|
* @apiError (client) QuotaReached Quota limit reached
|
|
|
|
* @apiError (server) FileError Error adding a file
|
|
|
|
* @apiPermission user
|
|
|
|
* @apiVersion 1.5.0
|
|
|
|
*
|
2013-08-13 20:20:28 +02:00
|
|
|
* @param documentId Document ID
|
2013-07-27 18:33:20 +02:00
|
|
|
* @param fileBodyPart File to add
|
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@PUT
|
|
|
|
@Consumes("multipart/form-data")
|
|
|
|
public Response add(
|
|
|
|
@FormDataParam("id") String documentId,
|
2015-09-07 21:51:13 +02:00
|
|
|
@FormDataParam("file") FormDataBodyPart fileBodyPart) {
|
2013-07-27 18:33:20 +02:00
|
|
|
if (!authenticate()) {
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate input data
|
|
|
|
ValidationUtil.validateRequired(fileBodyPart, "file");
|
|
|
|
|
2015-03-03 00:23:30 +01:00
|
|
|
// Get the document
|
2016-03-14 01:39:29 +01:00
|
|
|
DocumentDto documentDto = null;
|
2015-03-03 00:23:30 +01:00
|
|
|
if (Strings.isNullOrEmpty(documentId)) {
|
|
|
|
documentId = null;
|
|
|
|
} else {
|
|
|
|
DocumentDao documentDao = new DocumentDao();
|
2016-03-15 22:44:50 +01:00
|
|
|
documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
|
2016-03-14 01:39:29 +01:00
|
|
|
if (documentDto == null) {
|
2016-05-06 00:36:54 +02:00
|
|
|
throw new NotFoundException();
|
2015-03-03 00:23:30 +01:00
|
|
|
}
|
2013-07-27 18:33:20 +02:00
|
|
|
}
|
|
|
|
|
2018-02-22 10:46:32 +01:00
|
|
|
// Keep unencrypted data temporary on disk
|
2017-11-06 16:45:47 +01:00
|
|
|
java.nio.file.Path unencryptedFile;
|
|
|
|
long fileSize;
|
2013-08-20 21:51:07 +02:00
|
|
|
try {
|
2018-04-05 12:45:04 +02:00
|
|
|
unencryptedFile = AppContext.getInstance().getFileService().createTemporaryFile();
|
2017-11-06 16:45:47 +01:00
|
|
|
Files.copy(fileBodyPart.getValueAs(InputStream.class), unencryptedFile, StandardCopyOption.REPLACE_EXISTING);
|
|
|
|
fileSize = Files.size(unencryptedFile);
|
2013-08-20 21:51:07 +02:00
|
|
|
} catch (IOException e) {
|
|
|
|
throw new ServerException("StreamError", "Error reading the input file", e);
|
|
|
|
}
|
2017-11-06 16:45:47 +01:00
|
|
|
|
2013-07-28 01:07:04 +02:00
|
|
|
try {
|
2018-02-22 10:46:32 +01:00
|
|
|
String name = fileBodyPart.getContentDisposition() != null ?
|
2018-03-23 16:41:02 +01:00
|
|
|
URLDecoder.decode(fileBodyPart.getContentDisposition().getFileName(), "UTF-8") : null;
|
2018-02-22 10:46:32 +01:00
|
|
|
String fileId = FileUtil.createFile(name, unencryptedFile, fileSize, documentDto == null ?
|
|
|
|
null : documentDto.getLanguage(), principal.getId(), documentId);
|
2013-07-27 18:33:20 +02:00
|
|
|
|
2015-09-07 21:51:13 +02:00
|
|
|
// Always return OK
|
|
|
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
|
|
.add("status", "ok")
|
2015-11-30 00:08:47 +01:00
|
|
|
.add("id", fileId)
|
2017-11-06 16:45:47 +01:00
|
|
|
.add("size", fileSize);
|
2015-09-07 21:51:13 +02:00
|
|
|
return Response.ok().entity(response.build()).build();
|
2018-02-22 10:46:32 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
throw new ClientException(e.getMessage(), e.getMessage(), e);
|
2013-07-27 18:33:20 +02:00
|
|
|
} catch (Exception e) {
|
|
|
|
throw new ServerException("FileError", "Error adding a file", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-03 00:23:30 +01:00
|
|
|
/**
|
|
|
|
* Attach a file to a document.
|
2016-05-12 01:26:02 +02:00
|
|
|
*
|
2018-03-23 12:52:42 +01:00
|
|
|
* @api {post} /file/:fileId/attach Attach a file to a document
|
|
|
|
* @apiName PostFileAttach
|
2016-05-12 01:26:02 +02:00
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} fileId File ID
|
|
|
|
* @apiParam {String} id Document ID
|
|
|
|
* @apiSuccess {String} status Status OK
|
|
|
|
* @apiError (client) ForbiddenError Access denied
|
|
|
|
* @apiError (client) ValidationError Validation error
|
|
|
|
* @apiError (client) IllegalFile File not orphan
|
|
|
|
* @apiError (server) AttachError Error attaching file to document
|
|
|
|
* @apiPermission user
|
|
|
|
* @apiVersion 1.5.0
|
|
|
|
*
|
2015-03-03 00:23:30 +01:00
|
|
|
* @param id File ID
|
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@POST
|
2018-03-23 12:52:42 +01:00
|
|
|
@Path("{id: [a-z0-9\\-]+}/attach")
|
2015-03-03 00:23:30 +01:00
|
|
|
public Response attach(
|
|
|
|
@PathParam("id") String id,
|
2015-09-07 21:51:13 +02:00
|
|
|
@FormParam("id") String documentId) {
|
2015-03-03 00:23:30 +01:00
|
|
|
if (!authenticate()) {
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate input data
|
2018-03-29 11:34:25 +02:00
|
|
|
ValidationUtil.validateRequired(documentId, "documentId");
|
2015-03-03 00:23:30 +01:00
|
|
|
|
|
|
|
// Get the current user
|
|
|
|
UserDao userDao = new UserDao();
|
|
|
|
User user = userDao.getById(principal.getId());
|
|
|
|
|
|
|
|
// Get the document and the file
|
|
|
|
DocumentDao documentDao = new DocumentDao();
|
|
|
|
FileDao fileDao = new FileDao();
|
2015-11-16 02:22:51 +01:00
|
|
|
File file = fileDao.getFile(id, principal.getId());
|
2016-03-15 22:44:50 +01:00
|
|
|
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
|
2016-03-14 01:39:29 +01:00
|
|
|
if (file == null || documentDto == null) {
|
2016-05-06 00:36:54 +02:00
|
|
|
throw new NotFoundException();
|
2015-03-03 00:23:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the file is orphan
|
|
|
|
if (file.getDocumentId() != null) {
|
|
|
|
throw new ClientException("IllegalFile", MessageFormat.format("File not orphan: {0}", id));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the file
|
|
|
|
file.setDocumentId(documentId);
|
2015-03-06 22:40:33 +01:00
|
|
|
file.setOrder(fileDao.getByDocumentId(principal.getId(), documentId).size());
|
2015-03-06 21:13:09 +01:00
|
|
|
fileDao.update(file);
|
2015-03-03 00:23:30 +01:00
|
|
|
|
2018-03-29 11:34:25 +02:00
|
|
|
// Raise a new file updated event and document updated event (it wasn't sent during file creation)
|
2015-03-03 00:23:30 +01:00
|
|
|
try {
|
2015-11-29 19:42:49 +01:00
|
|
|
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
|
2017-11-06 16:45:47 +01:00
|
|
|
java.nio.file.Path unencryptedFile = EncryptionUtil.decryptFile(storedFile, user.getPrivateKey());
|
2018-03-13 23:32:05 +01:00
|
|
|
FileUtil.startProcessingFile(id);
|
2018-03-29 11:34:25 +02:00
|
|
|
FileUpdatedAsyncEvent fileUpdatedAsyncEvent = new FileUpdatedAsyncEvent();
|
|
|
|
fileUpdatedAsyncEvent.setUserId(principal.getId());
|
|
|
|
fileUpdatedAsyncEvent.setLanguage(documentDto.getLanguage());
|
|
|
|
fileUpdatedAsyncEvent.setFile(file);
|
|
|
|
fileUpdatedAsyncEvent.setUnencryptedFile(unencryptedFile);
|
|
|
|
ThreadLocalContext.get().addAsyncEvent(fileUpdatedAsyncEvent);
|
2016-02-21 14:21:20 +01:00
|
|
|
|
|
|
|
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
|
|
|
|
documentUpdatedAsyncEvent.setUserId(principal.getId());
|
2016-03-14 01:39:29 +01:00
|
|
|
documentUpdatedAsyncEvent.setDocumentId(documentId);
|
2016-08-26 21:22:27 +02:00
|
|
|
ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent);
|
2015-03-03 00:23:30 +01:00
|
|
|
} catch (Exception e) {
|
2016-05-12 01:26:02 +02:00
|
|
|
throw new ServerException("AttachError", "Error attaching file to document", e);
|
2015-03-03 00:23:30 +01:00
|
|
|
}
|
2018-03-29 11:34:25 +02:00
|
|
|
|
2015-09-07 21:51:13 +02:00
|
|
|
// Always return OK
|
|
|
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
|
|
.add("status", "ok");
|
|
|
|
return Response.ok().entity(response.build()).build();
|
2015-03-03 00:23:30 +01:00
|
|
|
}
|
2018-03-23 12:52:42 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Update a file.
|
|
|
|
*
|
|
|
|
* @api {post} /file/:id Update a file
|
|
|
|
* @apiName PostFile
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id File ID
|
|
|
|
* @apiParam {String} name Name
|
|
|
|
* @apiSuccess {String} status Status OK
|
|
|
|
* @apiError (client) ForbiddenError Access denied
|
|
|
|
* @apiError (client) ValidationError Validation error
|
|
|
|
* @apiPermission user
|
|
|
|
* @apiVersion 1.6.0
|
|
|
|
*
|
|
|
|
* @param id File ID
|
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@POST
|
|
|
|
@Path("{id: [a-z0-9\\-]+}")
|
|
|
|
public Response update(@PathParam("id") String id,
|
|
|
|
@FormParam("name") String name) {
|
|
|
|
if (!authenticate()) {
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the file
|
|
|
|
File file = findFile(id, null);
|
|
|
|
|
|
|
|
// Validate input data
|
|
|
|
name = ValidationUtil.validateLength(name, "name", 1, 200, false);
|
|
|
|
|
|
|
|
// Update the file
|
|
|
|
FileDao fileDao = new FileDao();
|
|
|
|
file.setName(name);
|
|
|
|
fileDao.update(file);
|
|
|
|
|
|
|
|
// Always return OK
|
2018-03-29 11:34:25 +02:00
|
|
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
|
|
.add("status", "ok");
|
|
|
|
return Response.ok().entity(response.build()).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process a file manually.
|
|
|
|
*
|
|
|
|
* @api {post} /file/:id/process Process a file manually
|
|
|
|
* @apiName PostFileProcess
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id File ID
|
|
|
|
* @apiSuccess {String} status Status OK
|
|
|
|
* @apiError (client) ForbiddenError Access denied
|
|
|
|
* @apiError (client) ValidationError Validation error
|
|
|
|
* @apiError (server) ProcessingError Processing error
|
|
|
|
* @apiPermission user
|
|
|
|
* @apiVersion 1.6.0
|
|
|
|
*
|
|
|
|
* @param id File ID
|
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@POST
|
|
|
|
@Path("{id: [a-z0-9\\-]+}/process")
|
|
|
|
public Response process(@PathParam("id") String id) {
|
|
|
|
if (!authenticate()) {
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the current user
|
|
|
|
UserDao userDao = new UserDao();
|
|
|
|
User user = userDao.getById(principal.getId());
|
|
|
|
|
|
|
|
// Get the document and the file
|
|
|
|
DocumentDao documentDao = new DocumentDao();
|
|
|
|
FileDao fileDao = new FileDao();
|
|
|
|
File file = fileDao.getFile(id);
|
|
|
|
if (file == null) {
|
|
|
|
throw new NotFoundException();
|
|
|
|
}
|
|
|
|
DocumentDto documentDto = documentDao.getDocument(file.getDocumentId(), PermType.WRITE, getTargetIdList(null));
|
|
|
|
if (documentDto == null) {
|
|
|
|
throw new NotFoundException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the processing asynchronously
|
|
|
|
try {
|
|
|
|
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
|
|
|
|
java.nio.file.Path unencryptedFile = EncryptionUtil.decryptFile(storedFile, user.getPrivateKey());
|
|
|
|
FileUtil.startProcessingFile(id);
|
|
|
|
FileUpdatedAsyncEvent fileUpdatedAsyncEvent = new FileUpdatedAsyncEvent();
|
|
|
|
fileUpdatedAsyncEvent.setUserId(principal.getId());
|
|
|
|
fileUpdatedAsyncEvent.setLanguage(documentDto.getLanguage());
|
|
|
|
fileUpdatedAsyncEvent.setFile(file);
|
|
|
|
fileUpdatedAsyncEvent.setUnencryptedFile(unencryptedFile);
|
|
|
|
ThreadLocalContext.get().addAsyncEvent(fileUpdatedAsyncEvent);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new ServerException("ProcessingError", "Error processing this file", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always return OK
|
2018-03-23 12:52:42 +01:00
|
|
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
|
|
.add("status", "ok");
|
|
|
|
return Response.ok().entity(response.build()).build();
|
|
|
|
}
|
2015-03-03 00:23:30 +01:00
|
|
|
|
2013-08-03 20:19:02 +02:00
|
|
|
/**
|
|
|
|
* Reorder files.
|
2016-05-12 01:26:02 +02:00
|
|
|
*
|
|
|
|
* @api {post} /file/:reorder Reorder files
|
|
|
|
* @apiName PostFileReorder
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id Document ID
|
|
|
|
* @apiParam {String[]} order List of files ID
|
|
|
|
* @apiSuccess {String} status Status OK
|
|
|
|
* @apiError (client) ForbiddenError Access denied
|
|
|
|
* @apiError (client) ValidationError Validation error
|
|
|
|
* @apiError (client) NotFound Document not found
|
|
|
|
* @apiPermission user
|
|
|
|
* @apiVersion 1.5.0
|
|
|
|
*
|
2013-08-13 20:20:28 +02:00
|
|
|
* @param documentId Document ID
|
|
|
|
* @param idList List of files ID in the new order
|
2013-08-03 20:19:02 +02:00
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@POST
|
|
|
|
@Path("reorder")
|
|
|
|
public Response reorder(
|
|
|
|
@FormParam("id") String documentId,
|
2015-09-07 21:51:13 +02:00
|
|
|
@FormParam("order") List<String> idList) {
|
2013-08-03 20:19:02 +02:00
|
|
|
if (!authenticate()) {
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate input data
|
|
|
|
ValidationUtil.validateRequired(documentId, "id");
|
|
|
|
ValidationUtil.validateRequired(idList, "order");
|
|
|
|
|
2013-08-03 21:51:15 +02:00
|
|
|
// Get the document
|
2016-05-08 12:14:06 +02:00
|
|
|
AclDao aclDao = new AclDao();
|
|
|
|
if (!aclDao.checkPermission(documentId, PermType.WRITE, getTargetIdList(null))) {
|
2016-05-06 00:36:54 +02:00
|
|
|
throw new NotFoundException();
|
2013-08-03 21:51:15 +02:00
|
|
|
}
|
|
|
|
|
2013-08-03 20:19:02 +02:00
|
|
|
// Reorder files
|
|
|
|
FileDao fileDao = new FileDao();
|
2015-03-06 22:40:33 +01:00
|
|
|
for (File file : fileDao.getByDocumentId(principal.getId(), documentId)) {
|
2013-08-03 20:19:02 +02:00
|
|
|
int order = idList.lastIndexOf(file.getId());
|
|
|
|
if (order != -1) {
|
|
|
|
file.setOrder(order);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-07 21:51:13 +02:00
|
|
|
// Always return OK
|
|
|
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
|
|
.add("status", "ok");
|
|
|
|
return Response.ok().entity(response.build()).build();
|
2013-08-03 20:19:02 +02:00
|
|
|
}
|
|
|
|
|
2013-07-27 18:33:20 +02:00
|
|
|
/**
|
2015-03-06 22:40:33 +01:00
|
|
|
* Returns files linked to a document or not linked to any document.
|
2016-05-12 01:26:02 +02:00
|
|
|
*
|
|
|
|
* @api {post} /file/list Get files
|
|
|
|
* @apiName GetFileList
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id Document ID
|
|
|
|
* @apiParam {String} share Share ID
|
|
|
|
* @apiSuccess {Object[]} files List of files
|
|
|
|
* @apiSuccess {String} files.id ID
|
|
|
|
* @apiSuccess {String} files.mimetype MIME type
|
2016-12-07 01:28:52 +01:00
|
|
|
* @apiSuccess {String} files.name File name
|
2018-03-13 23:32:05 +01:00
|
|
|
* @apiSuccess {String} files.processing True if the file is currently processing
|
2016-05-12 01:26:02 +02:00
|
|
|
* @apiSuccess {String} files.document_id Document ID
|
|
|
|
* @apiSuccess {String} files.create_date Create date (timestamp)
|
|
|
|
* @apiSuccess {String} files.size File size (in bytes)
|
|
|
|
* @apiError (client) ForbiddenError Access denied
|
|
|
|
* @apiError (client) NotFound Document not found
|
|
|
|
* @apiError (server) FileError Unable to get the size of a file
|
|
|
|
* @apiPermission none
|
|
|
|
* @apiVersion 1.5.0
|
|
|
|
*
|
2013-08-13 20:20:28 +02:00
|
|
|
* @param documentId Document ID
|
2015-03-06 22:40:33 +01:00
|
|
|
* @param shareId Sharing ID
|
2013-07-27 18:33:20 +02:00
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@GET
|
|
|
|
@Path("list")
|
|
|
|
public Response list(
|
2013-08-14 20:51:08 +02:00
|
|
|
@QueryParam("id") String documentId,
|
2015-09-07 21:51:13 +02:00
|
|
|
@QueryParam("share") String shareId) {
|
2015-03-06 21:23:50 +01:00
|
|
|
boolean authenticated = authenticate();
|
2013-08-14 20:51:08 +02:00
|
|
|
|
|
|
|
// Check document visibility
|
2015-03-06 21:23:50 +01:00
|
|
|
if (documentId != null) {
|
2015-08-26 22:11:39 +02:00
|
|
|
AclDao aclDao = new AclDao();
|
2016-03-15 22:44:50 +01:00
|
|
|
if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(shareId))) {
|
2016-05-06 00:36:54 +02:00
|
|
|
throw new NotFoundException();
|
2013-08-15 15:23:41 +02:00
|
|
|
}
|
2015-03-06 21:23:50 +01:00
|
|
|
} else if (!authenticated) {
|
|
|
|
throw new ForbiddenClientException();
|
2013-07-27 18:33:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
FileDao fileDao = new FileDao();
|
2015-03-06 22:40:33 +01:00
|
|
|
List<File> fileList = fileDao.getByDocumentId(principal.getId(), documentId);
|
2013-07-27 18:33:20 +02:00
|
|
|
|
2015-09-07 21:51:13 +02:00
|
|
|
JsonArrayBuilder files = Json.createArrayBuilder();
|
2013-07-27 18:33:20 +02:00
|
|
|
for (File fileDb : fileList) {
|
2015-11-30 00:08:47 +01:00
|
|
|
try {
|
|
|
|
files.add(Json.createObjectBuilder()
|
|
|
|
.add("id", fileDb.getId())
|
2018-03-13 23:32:05 +01:00
|
|
|
.add("processing", FileUtil.isProcessingFile(fileDb.getId()))
|
2016-12-07 01:28:52 +01:00
|
|
|
.add("name", JsonUtil.nullable(fileDb.getName()))
|
2015-11-30 00:08:47 +01:00
|
|
|
.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);
|
|
|
|
}
|
2013-07-27 18:33:20 +02:00
|
|
|
}
|
|
|
|
|
2015-09-07 21:51:13 +02:00
|
|
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
|
|
.add("files", files);
|
|
|
|
return Response.ok().entity(response.build()).build();
|
2013-07-27 18:33:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes a file.
|
2016-05-12 01:26:02 +02:00
|
|
|
*
|
|
|
|
* @api {delete} /file/:id Delete a file
|
|
|
|
* @apiName DeleteFile
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id File ID
|
|
|
|
* @apiParam {String} share Share ID
|
|
|
|
* @apiSuccess {String} status Status OK
|
|
|
|
* @apiError (client) ForbiddenError Access denied
|
|
|
|
* @apiError (client) NotFound File or document not found
|
|
|
|
* @apiPermission user
|
|
|
|
* @apiVersion 1.5.0
|
|
|
|
*
|
2013-07-27 18:33:20 +02:00
|
|
|
* @param id File ID
|
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@DELETE
|
|
|
|
@Path("{id: [a-z0-9\\-]+}")
|
|
|
|
public Response delete(
|
2015-09-07 21:51:13 +02:00
|
|
|
@PathParam("id") String id) {
|
2013-07-27 18:33:20 +02:00
|
|
|
if (!authenticate()) {
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the file
|
2018-03-23 12:52:42 +01:00
|
|
|
File file = findFile(id, null);
|
|
|
|
|
2013-08-19 15:27:34 +02:00
|
|
|
// Delete the file
|
2018-03-23 12:52:42 +01:00
|
|
|
FileDao fileDao = new FileDao();
|
2016-02-15 22:28:13 +01:00
|
|
|
fileDao.delete(file.getId(), principal.getId());
|
2013-08-19 15:27:34 +02:00
|
|
|
|
2015-11-29 19:42:49 +01:00
|
|
|
// Update the user quota
|
|
|
|
UserDao userDao = new UserDao();
|
|
|
|
User user = userDao.getById(principal.getId());
|
|
|
|
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
|
|
|
|
try {
|
|
|
|
user.setStorageCurrent(user.getStorageCurrent() - Files.size(storedFile));
|
2015-12-12 01:56:54 +01:00
|
|
|
userDao.updateQuota(user);
|
2015-11-29 19:42:49 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
// The file doesn't exists on disk, which is weird, but not fatal
|
|
|
|
}
|
|
|
|
|
2013-08-17 14:16:55 +02:00
|
|
|
// Raise a new file deleted event
|
|
|
|
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
2016-02-21 14:11:17 +01:00
|
|
|
fileDeletedAsyncEvent.setUserId(principal.getId());
|
2013-08-17 14:16:55 +02:00
|
|
|
fileDeletedAsyncEvent.setFile(file);
|
2016-08-26 21:22:27 +02:00
|
|
|
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
|
2013-08-17 14:16:55 +02:00
|
|
|
|
2016-05-08 12:14:06 +02:00
|
|
|
if (file.getDocumentId() != null) {
|
2016-02-21 14:21:20 +01:00
|
|
|
// Raise a new document updated
|
|
|
|
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
|
|
|
|
documentUpdatedAsyncEvent.setUserId(principal.getId());
|
2016-05-08 12:14:06 +02:00
|
|
|
documentUpdatedAsyncEvent.setDocumentId(file.getDocumentId());
|
2016-08-26 21:22:27 +02:00
|
|
|
ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent);
|
2016-02-21 14:21:20 +01:00
|
|
|
}
|
|
|
|
|
2015-09-07 21:51:13 +02:00
|
|
|
// Always return OK
|
|
|
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
|
|
.add("status", "ok");
|
|
|
|
return Response.ok().entity(response.build()).build();
|
2013-07-27 18:33:20 +02:00
|
|
|
}
|
2013-07-28 01:07:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a file.
|
2016-05-12 01:26:02 +02:00
|
|
|
*
|
|
|
|
* @api {delete} /file/:id/data Get a file data
|
|
|
|
* @apiName GetFile
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id File ID
|
|
|
|
* @apiParam {String} share Share ID
|
2018-03-23 16:41:02 +01:00
|
|
|
* @apiParam {String="web","thumb","content"} [size] Size variation
|
2016-05-12 01:26:02 +02:00
|
|
|
* @apiSuccess {Object} file The file data is the whole response
|
|
|
|
* @apiError (client) SizeError Size must be web or thumb
|
|
|
|
* @apiError (client) ForbiddenError Access denied or document not visible
|
|
|
|
* @apiError (client) NotFound File not found
|
|
|
|
* @apiError (server) ServiceUnavailable Error reading the file
|
|
|
|
* @apiPermission none
|
|
|
|
* @apiVersion 1.5.0
|
|
|
|
*
|
2013-08-14 00:47:58 +02:00
|
|
|
* @param fileId File ID
|
2013-07-28 01:07:04 +02:00
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@GET
|
|
|
|
@Path("{id: [a-z0-9\\-]+}/data")
|
|
|
|
public Response data(
|
2013-08-14 00:47:58 +02:00
|
|
|
@PathParam("id") final String fileId,
|
2013-08-14 20:51:08 +02:00
|
|
|
@QueryParam("share") String shareId,
|
2015-09-07 21:51:13 +02:00
|
|
|
@QueryParam("size") String size) {
|
2013-08-14 20:51:08 +02:00
|
|
|
authenticate();
|
2013-07-28 01:07:04 +02:00
|
|
|
|
2018-03-23 16:41:02 +01:00
|
|
|
if (size != null && !Lists.newArrayList("web", "thumb", "content").contains(size)) {
|
|
|
|
throw new ClientException("SizeError", "Size must be web, thumb or content");
|
2013-08-18 01:26:34 +02:00
|
|
|
}
|
2018-03-23 16:41:02 +01:00
|
|
|
|
2013-07-28 01:07:04 +02:00
|
|
|
// Get the file
|
2018-03-23 12:52:42 +01:00
|
|
|
File file = findFile(fileId, shareId);
|
2013-07-28 18:29:03 +02:00
|
|
|
|
|
|
|
// Get the stored file
|
2018-03-23 12:52:42 +01:00
|
|
|
UserDao userDao = new UserDao();
|
2015-11-29 19:42:49 +01:00
|
|
|
java.nio.file.Path storedFile;
|
2013-08-18 14:48:48 +02:00
|
|
|
String mimeType;
|
2016-05-06 00:36:54 +02:00
|
|
|
boolean decrypt;
|
2013-08-18 01:26:34 +02:00
|
|
|
if (size != null) {
|
2018-03-23 16:41:02 +01:00
|
|
|
if (size.equals("content")) {
|
|
|
|
return Response.ok(Strings.nullToEmpty(file.getContent()))
|
|
|
|
.header(HttpHeaders.CONTENT_TYPE, "text/plain")
|
|
|
|
.build();
|
|
|
|
}
|
|
|
|
|
2015-11-29 19:42:49 +01:00
|
|
|
storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_" + size);
|
2013-08-18 14:48:48 +02:00
|
|
|
mimeType = MimeType.IMAGE_JPEG; // Thumbnails are JPEG
|
2013-08-20 21:51:07 +02:00
|
|
|
decrypt = true; // Thumbnails are encrypted
|
2015-11-29 19:42:49 +01:00
|
|
|
if (!Files.exists(storedFile)) {
|
2016-11-20 18:41:42 +01:00
|
|
|
try {
|
2018-03-14 20:39:32 +01:00
|
|
|
storedFile = Paths.get(getClass().getResource("/image/file-" + size + ".png").toURI());
|
2016-11-20 18:41:42 +01:00
|
|
|
} catch (URISyntaxException e) {
|
|
|
|
// Ignore
|
|
|
|
}
|
2013-08-18 14:48:48 +02:00
|
|
|
mimeType = MimeType.IMAGE_PNG;
|
2013-08-21 01:05:59 +02:00
|
|
|
decrypt = false;
|
2013-07-28 01:07:04 +02:00
|
|
|
}
|
2013-07-28 18:29:03 +02:00
|
|
|
} else {
|
2015-11-29 19:42:49 +01:00
|
|
|
storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId);
|
2013-08-18 14:48:48 +02:00
|
|
|
mimeType = file.getMimeType();
|
2013-08-20 21:51:07 +02:00
|
|
|
decrypt = true; // Original files are encrypted
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stream the output and decrypt it if necessary
|
|
|
|
StreamingOutput stream;
|
2015-11-29 19:42:49 +01:00
|
|
|
|
2015-05-09 14:44:19 +02:00
|
|
|
// A file is always encrypted by the creator of it
|
|
|
|
User user = userDao.getById(file.getUserId());
|
2015-11-29 19:42:49 +01:00
|
|
|
|
|
|
|
// Write the decrypted file to the output
|
2013-08-20 21:51:07 +02:00
|
|
|
try {
|
2015-11-29 19:42:49 +01:00
|
|
|
InputStream fileInputStream = Files.newInputStream(storedFile);
|
2013-08-20 21:51:07 +02:00
|
|
|
final InputStream responseInputStream = decrypt ?
|
|
|
|
EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey()) : fileInputStream;
|
|
|
|
|
2018-03-18 22:21:31 +01:00
|
|
|
stream = outputStream -> {
|
|
|
|
try {
|
|
|
|
ByteStreams.copy(responseInputStream, outputStream);
|
|
|
|
} finally {
|
2013-08-20 21:51:07 +02:00
|
|
|
try {
|
2018-03-18 22:21:31 +01:00
|
|
|
responseInputStream.close();
|
|
|
|
outputStream.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
// Ignore
|
2013-08-20 21:51:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} catch (Exception e) {
|
2015-05-23 19:16:38 +02:00
|
|
|
return Response.status(Status.SERVICE_UNAVAILABLE).build();
|
2013-07-28 18:29:03 +02:00
|
|
|
}
|
2013-07-28 01:07:04 +02:00
|
|
|
|
2018-03-14 19:01:28 +01:00
|
|
|
Response.ResponseBuilder builder = Response.ok(stream)
|
2018-03-26 17:15:36 +02:00
|
|
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + file.getFullName("data") + "\"")
|
2018-03-14 19:01:28 +01:00
|
|
|
.header(HttpHeaders.CONTENT_TYPE, mimeType);
|
|
|
|
if (decrypt) {
|
|
|
|
// Cache real files
|
|
|
|
builder.header(HttpHeaders.CACHE_CONTROL, "private")
|
|
|
|
.header(HttpHeaders.EXPIRES, HttpUtil.buildExpiresHeader(3_600_000L * 24L * 365L));
|
|
|
|
} else {
|
|
|
|
// Do not cache the temporary thumbnail
|
|
|
|
builder.header(HttpHeaders.CACHE_CONTROL, "no-store, must-revalidate")
|
|
|
|
.header(HttpHeaders.EXPIRES, "0");
|
|
|
|
}
|
|
|
|
return builder.build();
|
2013-07-28 01:07:04 +02:00
|
|
|
}
|
2018-03-23 12:52:42 +01:00
|
|
|
|
2014-02-23 14:09:41 +01:00
|
|
|
/**
|
|
|
|
* Returns all files from a document, zipped.
|
2016-05-12 01:26:02 +02:00
|
|
|
*
|
|
|
|
* @api {get} /file/zip Get zipped files
|
|
|
|
* @apiName GetFileZip
|
|
|
|
* @apiGroup File
|
|
|
|
* @apiParam {String} id Document ID
|
|
|
|
* @apiParam {String} share Share ID
|
|
|
|
* @apiSuccess {Object} file The ZIP file is the whole response
|
|
|
|
* @apiError (client) NotFound Document not found
|
|
|
|
* @apiError (server) InternalServerError Error creating the ZIP file
|
|
|
|
* @apiPermission none
|
|
|
|
* @apiVersion 1.5.0
|
|
|
|
*
|
2014-02-23 14:09:41 +01:00
|
|
|
* @param documentId Document ID
|
|
|
|
* @return Response
|
|
|
|
*/
|
|
|
|
@GET
|
|
|
|
@Path("zip")
|
|
|
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
|
|
|
public Response zip(
|
|
|
|
@QueryParam("id") String documentId,
|
2015-09-07 21:51:13 +02:00
|
|
|
@QueryParam("share") String shareId) {
|
2014-02-23 14:09:41 +01:00
|
|
|
authenticate();
|
|
|
|
|
|
|
|
// Get the document
|
|
|
|
DocumentDao documentDao = new DocumentDao();
|
2016-03-15 22:44:50 +01:00
|
|
|
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
|
2015-11-16 02:22:51 +01:00
|
|
|
if (documentDto == null) {
|
2016-05-06 00:36:54 +02:00
|
|
|
throw new NotFoundException();
|
2014-02-23 14:09:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get files and user associated with this document
|
|
|
|
FileDao fileDao = new FileDao();
|
2015-05-09 14:44:19 +02:00
|
|
|
final UserDao userDao = new UserDao();
|
2015-03-06 22:40:33 +01:00
|
|
|
final List<File> fileList = fileDao.getByDocumentId(principal.getId(), documentId);
|
2014-02-23 14:09:41 +01:00
|
|
|
|
|
|
|
// Create the ZIP stream
|
2018-03-18 22:21:31 +01:00
|
|
|
StreamingOutput stream = outputStream -> {
|
|
|
|
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
|
|
|
// Add each file to the ZIP stream
|
|
|
|
int index = 0;
|
|
|
|
for (File file : fileList) {
|
|
|
|
java.nio.file.Path storedfile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
|
|
|
|
InputStream fileInputStream = Files.newInputStream(storedfile);
|
|
|
|
|
|
|
|
// Add the decrypted file to the ZIP stream
|
|
|
|
// Files are encrypted by the creator of them
|
|
|
|
User user = userDao.getById(file.getUserId());
|
|
|
|
try (InputStream decryptedStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey())) {
|
|
|
|
ZipEntry zipEntry = new ZipEntry(index + "-" + file.getFullName(Integer.toString(index)));
|
|
|
|
zipOutputStream.putNextEntry(zipEntry);
|
|
|
|
ByteStreams.copy(decryptedStream, zipOutputStream);
|
|
|
|
zipOutputStream.closeEntry();
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new WebApplicationException(e);
|
2014-02-23 14:09:41 +01:00
|
|
|
}
|
2018-03-18 22:21:31 +01:00
|
|
|
index++;
|
2014-02-23 14:09:41 +01:00
|
|
|
}
|
|
|
|
}
|
2018-03-18 22:21:31 +01:00
|
|
|
outputStream.close();
|
2014-02-23 14:09:41 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// Write to the output
|
|
|
|
return Response.ok(stream)
|
|
|
|
.header("Content-Type", "application/zip")
|
2015-05-10 13:45:39 +02:00
|
|
|
.header("Content-Disposition", "attachment; filename=\"" + documentDto.getTitle().replaceAll("\\W+", "_") + ".zip\"")
|
2014-02-23 14:09:41 +01:00
|
|
|
.build();
|
|
|
|
}
|
2018-03-23 12:52:42 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Find a file with access rights checking.
|
|
|
|
*
|
|
|
|
* @param fileId File ID
|
|
|
|
* @param shareId Share ID
|
|
|
|
* @return File
|
|
|
|
*/
|
|
|
|
private File findFile(String fileId, String shareId) {
|
|
|
|
FileDao fileDao = new FileDao();
|
|
|
|
File file = fileDao.getFile(fileId);
|
|
|
|
if (file == null) {
|
|
|
|
throw new NotFoundException();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file.getDocumentId() == null) {
|
|
|
|
// It's an orphan file
|
|
|
|
if (!file.getUserId().equals(principal.getId())) {
|
|
|
|
// But not ours
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Check document accessibility
|
|
|
|
AclDao aclDao = new AclDao();
|
|
|
|
if (!aclDao.checkPermission(file.getDocumentId(), PermType.READ, getTargetIdList(shareId))) {
|
|
|
|
throw new ForbiddenClientException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return file;
|
|
|
|
}
|
2013-07-27 18:33:20 +02:00
|
|
|
}
|