/*
* LinShare is an open source filesharing software, part of the LinPKI software
* suite, developed by Linagora.
*
* Copyright (C) 2015 LINAGORA
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version, provided you comply with the Additional Terms applicable for
* LinShare software by Linagora pursuant to Section 7 of the GNU Affero General
* Public License, subsections (b), (c), and (e), pursuant to which you must
* notably (i) retain the display of the “LinShare™” trademark/logo at the top
* of the interface window, the display of the “You are using the Open Source
* and free version of LinShare™, powered by Linagora © 2009–2015. Contribute to
* Linshare R&D by subscribing to an Enterprise offer!” infobox and in the
* e-mails sent with the Program, (ii) retain all hypertext links between
* LinShare and linshare.org, between linagora.com and Linagora, and (iii)
* refrain from infringing Linagora intellectual property rights over its
* trademarks and commercial brands. Other Additional Terms apply, see
* <http://www.linagora.com/licenses/> for more details.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License and
* its applicable Additional Terms for LinShare along with this program. If not,
* see <http://www.gnu.org/licenses/> for the GNU Affero General Public License
* version 3 and <http://www.linagora.com/licenses/> for the Additional Terms
* applicable to LinShare software.
*/
package org.linagora.linshare.webservice.uploadrequest.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.ConcurrentMap;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import org.linagora.linshare.core.domain.objects.ChunkedFile;
import org.linagora.linshare.core.exception.BusinessException;
import org.linagora.linshare.core.facade.webservice.uploadrequest.UploadRequestUrlFacade;
import org.linagora.linshare.webservice.WebserviceBase;
import org.linagora.linshare.webservice.uploadrequest.FlowUploaderRestService;
import org.linagora.linshare.webservice.utils.FlowUploaderUtils;
import org.linagora.linshare.core.facade.webservice.common.dto.ErrorDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Maps;
import com.wordnik.swagger.annotations.Api;
@Path("/flow/upload")
@Api(value = "/rest/uploadrequest/flow/upload", description = "upload_requests API")
@Produces({ "application/json", "application/xml" })
public class FlowUploaderRestServiceImpl extends WebserviceBase implements
FlowUploaderRestService {
private static final Logger logger = LoggerFactory
.getLogger(FlowUploaderRestServiceImpl.class);
private static final String CHUNK_NUMBER = "flowChunkNumber";
private static final String TOTAL_CHUNKS = "flowTotalChunks";
private static final String CHUNK_SIZE = "flowChunkSize";
private static final String TOTAL_SIZE = "flowTotalSize";
private static final String IDENTIFIER = "flowIdentifier";
private static final String FILENAME = "flowFilename";
private static final String RELATIVE_PATH = "flowRelativePath";
private static final String FILE = "file";
private static final String PASSWORD = "password";
private static final String REQUEST_URL_UUID = "requestUrlUuid";
private static final String IEFILENAME = "filename";
private final UploadRequestUrlFacade uploadRequestUrlFacade;
private static final ConcurrentMap<String, ChunkedFile> chunkedFiles = Maps
.newConcurrentMap();
public FlowUploaderRestServiceImpl(
UploadRequestUrlFacade uploadRequestUrlFacade) {
super();
this.uploadRequestUrlFacade = uploadRequestUrlFacade;
}
@Path("/")
@GET
@Override
public Response testChunk(@QueryParam(CHUNK_NUMBER) long chunkNumber,
@QueryParam(TOTAL_CHUNKS) long totalChunks,
@QueryParam(CHUNK_SIZE) long chunkSize,
@QueryParam(TOTAL_SIZE) long totalSize,
@QueryParam(IDENTIFIER) String identifier,
@QueryParam(FILENAME) String filename,
@QueryParam(RELATIVE_PATH) String relativePath) {
return FlowUploaderUtils.testChunk(chunkNumber, totalChunks, chunkSize,
totalSize, identifier, filename, relativePath, chunkedFiles, false);
}
@Path("/")
@POST
@Consumes("multipart/form-data")
@Override
public Response uploadChunk(@Multipart(CHUNK_NUMBER) long chunkNumber,
@Multipart(TOTAL_CHUNKS) long totalChunks,
@Multipart(CHUNK_SIZE) long chunkSize,
@Multipart(TOTAL_SIZE) long totalSize,
@Multipart(IDENTIFIER) String identifier,
@Multipart(FILENAME) String filename,
@Multipart(RELATIVE_PATH) String relativePath,
@Multipart(FILE) InputStream file, MultipartBody body,
@Multipart(REQUEST_URL_UUID) String uploadRequestUrlUuid,
@Multipart(PASSWORD) String password) throws BusinessException {
logger.debug("upload chunk number : " + chunkNumber);
identifier = cleanIdentifier(identifier);
Validate.isTrue(isValid(chunkNumber, chunkSize, totalSize, identifier,
filename));
try {
logger.debug("writing chunk number : " + chunkNumber);
java.nio.file.Path tempFile = getTempFile(identifier);
FileChannel fc = FileChannel.open(tempFile,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
byte[] byteArray = IOUtils.toByteArray(file);
fc.write(ByteBuffer.wrap(byteArray), (chunkNumber - 1) * chunkSize);
fc.close();
chunkedFiles.get(identifier).addChunk(chunkNumber);
if (isUploadFinished(identifier, chunkSize, totalSize)) {
logger.debug("upload finished ");
InputStream inputStream = Files.newInputStream(tempFile,
StandardOpenOption.READ);
File tempFile2 = getTempFile(inputStream, "rest-flowuploader", filename);
try {
uploadRequestUrlFacade.addUploadRequestEntry(
uploadRequestUrlUuid, password, tempFile2, filename);
} finally {
deleteTempFile(tempFile2);
}
ChunkedFile remove = chunkedFiles.remove(identifier);
Files.deleteIfExists(remove.getPath());
return Response.ok("upload success").build();
} else {
logger.debug("upload pending ");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return Response.ok("upload success").build();
}
private java.nio.file.Path getTempFile(String identifier)
throws IOException {
ChunkedFile chunkedFile = chunkedFiles.get(identifier);
if (chunkedFile == null) {
java.nio.file.Path path = Files.createTempFile("ls-chunks-"
+ identifier, ".temp");
chunkedFiles.putIfAbsent(identifier, new ChunkedFile(path));
chunkedFile = chunkedFiles.get(identifier);
}
return chunkedFile.getPath();
}
/**
* UPLOAD FOR IE
*/
@Path("/iexplorer")
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_PLAIN)
@Override
public Response uploadForIe9(@Multipart(value = FILE) InputStream file,
MultipartBody body,
@Multipart(REQUEST_URL_UUID) String uploadRequestUrlUuid,
@Multipart(PASSWORD) String password) throws BusinessException {
if (file == null) {
throw giveRestException(HttpStatus.SC_BAD_REQUEST,
"Missing file (check parameter file)");
}
// Ensure fileName and description aren't null
String fileName = body.getAttachment(FILE).getContentDisposition()
.getParameter(IEFILENAME);
try {
byte[] bytes = fileName.getBytes("ISO-8859-1");
fileName = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e1) {
logger.error("Can not encode file name " + e1.getMessage());
}
ErrorDto errorDto;
try {
File tempFile2 = getTempFile(file, "rest-flowuploader", fileName);
try {
uploadRequestUrlFacade.addUploadRequestEntry(uploadRequestUrlUuid,
password, tempFile2, fileName);
} finally {
deleteTempFile(tempFile2);
}
errorDto = new ErrorDto(0, "upload success");
} catch (BusinessException exception) {
logger.error(exception.getMessage());
errorDto = new ErrorDto(exception.getErrorCode().getCode(),
exception.getMessage());
}
ResponseBuilder response = Response.status(Status.OK);
response.entity(errorDto);
return response.build();
}
/**
* HELPERS
*/
private String cleanIdentifier(String identifier) {
return identifier.replaceAll("[^0-9A-Za-z_-]", "");
}
private boolean isValid(long chunkNumber, long chunkSize, long totalSize,
String identifier, String filename) {
// Check if the request is sane
if (chunkNumber == 0 || chunkSize == 0 || totalSize == 0
|| identifier.length() == 0 || filename.length() == 0) {
return false;
}
double numberOfChunks = computeNumberOfChunks(chunkSize, totalSize);
if (chunkNumber > numberOfChunks) {
return false;
}
return true;
}
private double computeNumberOfChunks(long chunkSize, long totalSize) {
return Math.max(Math.floor(totalSize / chunkSize), 1);
}
private boolean isUploadFinished(String identifier, long chunkSize,
long totalSize) {
double numberOfChunks = computeNumberOfChunks(chunkSize, totalSize);
return chunkedFiles.get(identifier).getChunks().size() == numberOfChunks;
}
}