org.kawanfw.file.servlet.util.FileTransferManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of awake-file-server Show documentation
Show all versions of awake-file-server Show documentation
Awake FILE is a secure Open Source framework that allows to program very easily file uploads/downloads and RPC through http. File transfers include
powerful features like file chunking and automatic recovery mechanism.
Security has been taken into account from the design: server side allows
to specify strong security rules in order to protect the files and to secure the RPC calls.
The newest version!
/*
* This file is part of Awake FILE.
* Awake file: Easy file upload & download over HTTP with Java.
* Copyright (C) 2015, KawanSoft SAS
* (http://www.kawansoft.com). All rights reserved.
*
* Awake FILE is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Awake FILE 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Any modifications to this file must keep this entire header
* intact.
*/
package org.kawanfw.file.servlet.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Date;
import java.util.logging.Level;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.kawanfw.commons.server.util.ServerLogger;
import org.kawanfw.commons.util.DefaultParms;
import org.kawanfw.commons.util.FrameworkDebug;
import org.kawanfw.file.api.server.FileConfigurator;
/**
* The Action Manager for files transfer: define how all concrete operations will be done
* on files.
*
* @author Nicolas de Pomereu
* @since 1.0
*/
public class FileTransferManager {
private static boolean DEBUG = FrameworkDebug
.isSet(FileTransferManager.class);
private static final int EOF = -1;
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
/**
* Constructor.
*/
public FileTransferManager() {
}
/*
* (non-Javadoc)
*
* @see org.kawanfw.file.api.server.fileaction.FileActionManager#
* actionDownloadFile(java.io.OutputStream,
* org.kawanfw.file.api.server.FileConfigurator, java.lang.String,
* java.lang.String)
*/
public boolean download(OutputStream out,
FileConfigurator fileConfigurator, String username,
String filename, long chunkLength) throws FileNotFoundException,
IOException {
InputStream in = null;
debug(new Date() + " DOWNLOAD SESSION BEGIN ");
try {
filename = HttpConfigurationUtil.addRootPath(fileConfigurator,
username, filename);
debug(new Date() + " DOWNLOAD CHUNK");
// Do we must download a chunk only ? We will then seek the
// Random access file and read only one chunk length and send it
// back to client
if (filename.endsWith(".kawanfw.chunk")) {
// We are now in chunk case
String rawFilename = StringUtils.substringBeforeLast(filename,
".kawanfw.chunk");
String indexStr = StringUtils.substringAfterLast(rawFilename,
".");
int index = Integer.parseInt(indexStr);
// Remove the number
rawFilename = StringUtils.substringBeforeLast(rawFilename, ".");
// We seek the total length of previous files, because client
// method
// is idempotent and may be replayed
long lengthToSeek = (index - 1) * chunkLength;
// debug("index : " + index);
// debug("chunkLength : " + chunkLength);
// debug("lengthToSeek: " + lengthToSeek);
debug("");
debug(new Date() + " SESSION " + " " + index);
RandomAccessFile raf = null;
try {
File file = new File(rawFilename);
if (!file.exists()) {
debug("File does not exists: " + file);
return false;
}
debug(new Date() + " BEFORE SEEK ");
debug(new Date() + " BEFORE CREATE RAF");
raf = new RandomAccessFile(file, "rw");
debug(new Date() + " AFTER CREATE RAF");
raf.seek(lengthToSeek);
debug(new Date() + " BEFORE COPY ");
long totalRead = copy(raf, out, chunkLength);
debug(new Date() + " AFTER COPY " + totalRead);
IOUtils.closeQuietly(raf);
if (lengthToSeek + totalRead >= file.length()) {
// End of operations
// Nothing yo do with Random Access File
}
} finally {
IOUtils.closeQuietly(raf);
}
return true;
} else {
debug(new Date() + " DOWNLOAD FULL FILE");
File file = new File(filename);
if (!file.exists()) {
debug("File does not exists: " + file);
return false;
}
in = new BufferedInputStream(new FileInputStream(file));
IOUtils.copy(in, out);
}
return true;
} finally {
IOUtils.closeQuietly(in);
}
}
/*
* (non-Javadoc)
*
* @see org.kawanfw.file.api.server.fileaction.FileActionManager#
* actionUploadFile(org.kawanfw.file.api.server.FileConfigurator,
* java.io.InputStream, java.lang.String, java.lang.String)
*/
public void upload(FileConfigurator fileConfigurator,
InputStream inputStream, String username, String filename,
long chunkLength) throws IOException {
debug(new Date() + " UPLOAD SESSION BEGIN ");
filename = HttpConfigurationUtil.addRootPath(fileConfigurator,
username, filename);
// is it a file chunk? If yes append to filename
if (filename.endsWith(".kawanfw.chunk")
|| filename.endsWith(".kawanfw.chunk.LASTCHUNK")) {
RandomAccessFile raf = null;
try {
boolean lastChunk = false;
if (filename.endsWith(".LASTCHUNK")) {
debug(new Date() + " RENAME DONE");
filename = StringUtils.substringBeforeLast(filename,
".LASTCHUNK");
lastChunk = true;
}
initFileIfFirstChunk(username, filename);
String rawFilename = StringUtils.substringBeforeLast(filename,
".kawanfw.chunk");
String indexStr = StringUtils.substringAfterLast(rawFilename,
".");
// Remove the number
rawFilename = StringUtils.substringBeforeLast(rawFilename, ".");
int index = Integer.parseInt(indexStr);
File file = new File(rawFilename);
debug(new Date() + " SESSION INDEX" + " " + index);
// We must create, if necessary, the path to the file
createParentDir(file);
debug(new Date() + " BEFORE CREATE RAF");
raf = new RandomAccessFile(file, "rw");
debug(new Date() + " AFTER CREATE RAF");
// We seek the total length of previous files, because client
// method
// is idempotent and may be replayed
long lengthToSeek = (index - 1) * chunkLength;
// debug("index : " + index);
// debug("chunkLength : " + chunkLength);
// debug("lengthToSeek: " + lengthToSeek);
debug(new Date() + " BEFORE SEEK ");
raf.seek(lengthToSeek);
debug(new Date() + " BEFORE COPY ");
copy(inputStream, raf, new byte[DEFAULT_BUFFER_SIZE]);
debug(new Date() + " AFTER COPY ");
IOUtils.closeQuietly(raf);
if (lastChunk) {
// End of operations
// Do nothing with Random Access Files
}
} finally {
IOUtils.closeQuietly(raf);
}
} else {
OutputStream out = null;
try {
File file = new File(filename);
// We must create, if necessary, the path to the file
createParentDir(file);
out = new BufferedOutputStream(new FileOutputStream(file));
IOUtils.copy(inputStream, out);
debug("file created : " + file);
debug("file.length(): " + file.length());
} finally {
IOUtils.closeQuietly(out);
}
}
}
/**
* Copy the content of the random access file to the output stream up to a
* chubk length
*
* @param raf
* @param out
* @param chunkLength
* @return the total amount read
*
* @throws IOException
*/
private long copy(RandomAccessFile raf, OutputStream out, long chunkLength)
throws IOException {
int writeBufferSize = DefaultParms.DEFAULT_WRITE_BUFFER_SIZE;
// For Chunk Length comparison. If buffer read > chunk
// length ==> exit
long totalRead = 0;
byte[] tmp = new byte[writeBufferSize];
int len;
while ((len = raf.read(tmp)) >= 0) {
totalRead += len;
out.write(tmp, 0, len);
// Exit if chunk length is reached. We will just send
// the chunck,InputStream will be reused
if (chunkLength > 0 && totalRead > chunkLength - writeBufferSize) {
return totalRead;
}
}
return totalRead;
}
/**
* Copy the input stream into the raf
*
* @param input
* @param output
* @param buffer
* @return the lrngth written into the reaf
* @throws IOException
*/
private long copy(InputStream input, RandomAccessFile output, byte[] buffer)
throws IOException {
long count = 0;
int n = 0;
while (EOF != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
/**
* Create the parent directory of file, if necessary
*
* @param file
* the file to create for the parent directories if necessary
*/
public void createParentDir(File file) {
// We must create, if necessary, the path to the file
File parentDir = file.getParentFile();
if (parentDir != null) {
parentDir.mkdirs();
}
}
/**
* Delete the raw file if first chunk
*
* @param username
* the client username
* @param filename
* the chunk file name
*/
private void initFileIfFirstChunk(String username, String filename)
throws IOException {
if (filename.endsWith(".1.kawanfw.chunk")) {
String rawFilename = StringUtils.substringBeforeLast(filename,
".kawanfw.chunk");
rawFilename = StringUtils.substringBeforeLast(rawFilename, "."); // Remove
// the
// number
File file = new File(rawFilename);
if (file.exists()) {
boolean deleted = file.delete();
if (!deleted) {
throw new IOException(
"File delete required because of upload of first chunk. Impossible to delete file: "
+ file);
}
}
}
}
private void debug(String s) {
if (DEBUG) {
ServerLogger.getLogger().log(Level.WARNING, s);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy