/* (c) 2015 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.restupload;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resources;
import org.geoserver.rest.util.IOUtils;
import org.geoserver.rest.util.RESTUtils;
import org.geotools.util.logging.Logging;
import org.restlet.resource.Representation;
/**
* Manages resumable upload resource
*
* @author Nicola Lagomarsini
*/
public class ResumableUploadResourceManager {
/** LOGGER class */
private static final Logger LOGGER = Logging.getLogger(ResumableUploadResourceManager.class);
/** Resource folder for the temporary uploads */
private static Resource tmpUploadFolder;
public ResumableUploadResourceManager(String tmpFolder) {
GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class);
tmpUploadFolder = loader.get(tmpFolder);
}
public String createUploadResource(String filePath) throws IllegalStateException, IOException {
String uploadId = getUploadId();
ResumableUploadResource uploadResource = getResource(uploadId);
if (uploadResource != null) {
throw new IllegalStateException("The uploadId was already set!");
} else {
createUploadResource(filePath, uploadId);
}
return uploadId;
}
public Boolean hasAnyResource() {
Collection<File> files = FileUtils.listFiles(tmpUploadFolder.dir(), new WildcardFileFilter(
"*.*"), TrueFileFilter.INSTANCE);
return (files.size() != 0);
}
public Boolean resourceExists(String uploadId) {
return getResource(uploadId) != null;
}
public Long handleUpload(String uploadId, Representation entity, Long startPosition) {
ResumableUploadResource resource = getResource(uploadId);
Long writtenBytes = 0L;
try {
final ReadableByteChannel source = entity.getChannel();
RandomAccessFile raf = null;
FileChannel outputChannel = null;
try {
raf = new RandomAccessFile(resource.getFile(), "rw");
outputChannel = raf.getChannel();
writtenBytes = IOUtils.copyToFileChannel(256 * 1024, source, outputChannel,
startPosition);
} finally {
try {
if (raf != null) {
raf.close();
}
} finally {
IOUtils.closeQuietly(source);
IOUtils.closeQuietly(outputChannel);
}
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
} finally {
}
return resource.getFile().length();
}
/**
* Executes validations on resume parameters to check
* if successive start position index matches actual partial file length
*/
public Boolean validateUpload(String uploadId, Long totalByteToUpload, Long startPosition,
Long endPosition, Long totalFileSize) {
Boolean validated = false;
ResumableUploadResource uploadResource = getResource(uploadId);
if (uploadResource != null && uploadResource.getFile().exists()) {
if (uploadResource.getFile().length() == startPosition) {
validated = true;
}
}
return validated;
}
/**
* Create new temporary file for this uploadId
*/
public void clearUpload(String uploadId) {
ResumableUploadResource resource = getResource(uploadId);
if (resource != null) {
resource.clear();
}
}
/**
* Deletes all the file from temporary folder which aren't modified from more than expirationThreshold
*/
public void cleanExpiredResources(long expirationThreshold) {
Collection<File> files = FileUtils.listFiles(tmpUploadFolder.dir(), new WildcardFileFilter(
"*.*"), TrueFileFilter.INSTANCE);
for (Iterator<File> i = files.iterator(); i.hasNext();) {
File file = i.next();
if (file.lastModified() < expirationThreshold) {
file.delete();
}
}
}
public Long getWrittenBytes(String uploadId) {
return getResource(uploadId).getFile().length();
}
/**
* Executes the mapping to move uploaded file from temporary folder to REST upload root
* Creates the sidecar file
*/
public String uploadDone(String uploadId) throws IOException {
ResumableUploadResource resource = getResource(uploadId);
Map<String, String> storeParams = new HashMap<String, String>();
String destinationPath = getDestinationPath(uploadId);
StringBuilder remappingPath = new StringBuilder(destinationPath);
String tempFile = resource.getFile().getCanonicalPath();
RESTUtils.remapping(null, FilenameUtils.getBaseName(destinationPath),
remappingPath, tempFile, storeParams);
//Move file to remapped path
Resource destinationFile = Resources.fromPath(remappingPath.toString());
// Fill file
IOUtils.copyStream(new FileInputStream(resource.getFile()), destinationFile.out(), true, true);
resource.delete();
// Add temporary sidecar file to mark upload completion, it will be cleared after expirationThreshold
getSideCarFile(uploadId).createNewFile();
return destinationPath.toString();
}
/**
* Checks if upload with uploadId is terminated:</br>
* <ul>
* <li>if resource exists in temp folder the upload is not terminate
* <li>if sidecar file exists in temp folder the upload is terminated
* <li>if no resource or sidecar is found the resource is unknown
* </ul>
*/
public Boolean isUploadDone(String uploadId) throws IOException, IllegalStateException {
ResumableUploadResource resource = getResource(uploadId);
if (resource != null) {
return false;
} else {
if (getSideCarFile(uploadId).exists()) {
return true;
} else {
throw new IllegalStateException("Resource uploaded not found");
}
}
}
//Return relative path of uploaded file
private String getDestinationPath(String uploadId) throws IOException {
ResumableUploadResource resource = getResource(uploadId);
String fileName = resource.getFile().getCanonicalPath()
.replaceAll(tmpUploadFolder.dir().getCanonicalPath(), "");
fileName = fileName.replaceAll("_" + uploadId, "");
fileName = fileName.replaceAll("^/", "");
return fileName;
}
// Create a sidecar file associated to uploadId
private File getSideCarFile(String uploadId) throws IOException {
String sidecarPath = FilenameUtils.concat(tmpUploadFolder.dir().getCanonicalPath(),
uploadId + ".sidecar");
return new File(sidecarPath);
}
// Find resource with specific uploadId into temp folder
private ResumableUploadResource getResource(String uploadId) throws IllegalStateException {
Collection<File> files = FileUtils.listFiles(tmpUploadFolder.dir(), new WildcardFileFilter(
"*_" + uploadId + ".*"), TrueFileFilter.INSTANCE);
if (files.size() == 1) {
return new ResumableUploadResource(uploadId, files.iterator().next());
}
if (files.size() > 1) {
throw new IllegalStateException("Found multiple files with same uploadId");
}
return null;
}
// Create a file by append uploadId to filePath with "_" separator
private void createUploadResource(String filePath, String uploadId) throws IOException {
String tempPath = FilenameUtils.removeExtension(filePath) + "_" + uploadId + "."
+ FilenameUtils.getExtension(filePath);
tempPath = tempPath.replaceAll("^/", "");
tempPath = FilenameUtils.concat(tmpUploadFolder.dir().getCanonicalPath(), tempPath);
try {
new File(tempPath).getParentFile().mkdirs();
new File(tempPath).createNewFile();
} catch (IOException e) {
throw new IllegalStateException("Unable to create upload resource");
}
}
// Generate random uploadId
private String getUploadId() {
String id = UUID.randomUUID().toString();
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Associating resource with upload id: " + id);
}
return id;
}
private static final class ResumableUploadResource {
private String id;
private File file;
public ResumableUploadResource(String id, File file) {
this.id = id;
this.file = file;
}
public File getFile() {
return file;
}
public void delete() {
if (this.file.exists()) {
this.file.delete();
}
}
public void clear() {
if (this.file.exists()) {
this.file.delete();
}
try {
this.file.createNewFile();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
}
}
}