/******************************************************************************* * Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. *******************************************************************************/ package org.cloudifysource.rest.repo; import java.io.File; import java.io.IOException; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.PreDestroy; import org.apache.commons.io.FileUtils; import org.cloudifysource.dsl.internal.CloudifyConstants; import org.cloudifysource.dsl.internal.CloudifyMessageKeys; import org.cloudifysource.rest.controllers.RestErrorException; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; /** * A class for uploading files and getting uploaded files. * * @author yael * */ @Component public class UploadRepo { private static final Logger logger = Logger.getLogger(UploadRepo.class.getName()); private int uploadSizeLimitBytes = CloudifyConstants.DEFAULT_UPLOAD_SIZE_LIMIT_BYTES; private int cleanupTimeoutMillis = CloudifyConstants.DEFAULT_UPLOAD_TIMEOUT_MILLIS; private File baseDir; private ScheduledExecutorService executor; private File restUploadDir; /** * Initializing scheduled thread. * @throws RestErrorException * If failed to create upload directory. */ public void init() throws RestErrorException { log(Level.INFO, "Initializing upload repo."); createUploadDir(); createScheduledExecutor(); } private void createScheduledExecutor() { String absolutePath = null; if (restUploadDir != null) { absolutePath = restUploadDir.getAbsolutePath(); } log(Level.FINE, "[createScheduledExecutor] - " + "creating cleanup thread that will clean all files from rest upload directory [" + absolutePath + "] every " + cleanupTimeoutMillis + " millis."); final CleanUploadDirRunnable cleanupThread = new CleanUploadDirRunnable(restUploadDir, cleanupTimeoutMillis); executor = Executors.newSingleThreadScheduledExecutor(); try { executor.scheduleAtFixedRate(cleanupThread, 0, cleanupTimeoutMillis, TimeUnit.MILLISECONDS); } catch (final RejectedExecutionException e) { log(Level.WARNING, "failed to scheduled for execution - " + e.getMessage()); throw e; } } /** * destroy. * @throws IOException . */ @PreDestroy public void destroy() throws IOException { executor.shutdown(); FileUtils.deleteDirectory(restUploadDir); } private void reset() { executor.shutdownNow(); createScheduledExecutor(); } /** * Creating the upload directory. * * @throws RestErrorException * If failed to create upload directory. */ public void createUploadDir() throws RestErrorException { log(Level.FINER, "[createUploadDir] - creating rest uploads directory in - " + baseDir); restUploadDir = new File(baseDir, CloudifyConstants.UPLOADS_FOLDER_NAME); final String absolutePath = restUploadDir.getAbsolutePath(); log(Level.FINER, "[createUploadDir] - setting restUploadDir to: " + absolutePath); restUploadDir.deleteOnExit(); if (restUploadDir.exists()) { try { FileUtils.deleteDirectory(restUploadDir); } catch (IOException e) { log(Level.WARNING, "[createUploadDir] - failed to delete uploads directory [" + absolutePath + "]."); throw new RestErrorException( CloudifyMessageKeys.UPLOAD_DIRECTORY_CREATION_FAILED.getName(), absolutePath); } } final boolean mkdirs = restUploadDir.mkdirs(); if (mkdirs) { log(Level.FINE, "[createUploadDir] - created rest uploads directory - " + absolutePath); } else { log(Level.WARNING, "[createUploadDir] - failed to create rest uploads directory at " + absolutePath); throw new RestErrorException( CloudifyMessageKeys.UPLOAD_DIRECTORY_CREATION_FAILED.getName(), absolutePath); } } private void copyMultipartFileToLocalFile(final MultipartFile srcFile, final File storedFile) throws IOException { if (srcFile == null) { return; } srcFile.transferTo(storedFile); storedFile.deleteOnExit(); } /** * Creates a new folder with a randomly generated name (using the UUID class) which holds the uploaded file. The * folder located at the main upload folder in {@link #baseDir}. This uploaded file and its folder will be deleted * after {@link #cleanupTimeoutMillis} millis. * * @param fileName * The name of the uploaded file. If null, the multipartFile's original file name will be used as the * file's name. * @param multipartFile * The file to upload. * @return the uploaded key. * @throws RestErrorException * if the file doesn't end with zip. * @throws IOException . */ public String put(final String fileName, final MultipartFile multipartFile) throws IOException, RestErrorException { final String name = fileName == null ? multipartFile.getOriginalFilename() : fileName; // enforce size limit log(Level.FINER, "uploading file " + name); final long fileSize = multipartFile.getSize(); if (fileSize > getUploadSizeLimitBytes()) { log(Level.FINER, "Upload file [" + name + "] size (" + fileSize + ") exceeded the permitted size limit (" + getUploadSizeLimitBytes() + ")."); throw new RestErrorException( CloudifyMessageKeys.UPLOAD_FILE_SIZE_LIMIT_EXCEEDED.getName(), name, fileSize, getUploadSizeLimitBytes()); } final String dirName = UUID.randomUUID().toString(); final File srcDir = new File(restUploadDir, dirName); srcDir.mkdirs(); final File storedFile = new File(srcDir, name); log(Level.FINER, "Uploading file to " + storedFile.getAbsolutePath()); copyMultipartFileToLocalFile(multipartFile, storedFile); log(Level.FINER, "File [" + storedFile.getAbsolutePath() + "] uploaded successfully."); return dirName; } /** * Gets the file stored in a directory with the given name (uploadDirName). * * @param key * - the name of the upload file's directory. * @return the suitable file or null if a file with that name doesn't exist. */ public File get(final String key) { if (key == null) { log(Level.WARNING, "failed to get uploaded file, key is null."); return null; } log(Level.FINE, "Getting uploaded file with key " + key); if (restUploadDir == null) { log(Level.WARNING, "failed to get uploaded file, key is " + key + ", upload directory is null."); return null; } if (!restUploadDir.exists()) { log(Level.WARNING, "failed to get uploaded file. key is " + key + ", upload directory [" + restUploadDir.getAbsolutePath() + "] does not exist."); return null; } log(Level.FINER, "Trying to get the uploaded file stored in a directory named - " + key + " (under " + restUploadDir.getAbsolutePath() + ")."); final File dir = new File(restUploadDir, key); if (dir.exists()) { if (!dir.isDirectory()) { log(Level.WARNING, "The file found is not a directory [" + dir.getAbsolutePath() + "]."); return null; } final File[] listFiles = dir.listFiles(); if (listFiles.length > 0) { final File uploadedFile = listFiles[0]; log(Level.FINE, "Returning the found uploaded file [" + uploadedFile.getAbsolutePath() + "]."); return uploadedFile; } log(Level.WARNING, "The directory [" + dir.getAbsolutePath() + "] is empty."); } else { log(Level.WARNING, "No directory with name " + key + " was found at " + restUploadDir.getAbsolutePath()); } return null; } public File getRestUploadDir() { return restUploadDir; } /** * Sets the cleanup timeout and reset the scheduled thread. * * @param cleanupTimeoutMillis * . */ public void resetTimeout(final int cleanupTimeoutMillis) { log(Level.INFO, "reset timeout to " + cleanupTimeoutMillis + " milliseconds."); this.setCleanupTimeoutMillis(cleanupTimeoutMillis); reset(); } public File getBaseDir() { return baseDir; } public void setBaseDir(final File baseDir) { this.baseDir = baseDir; } public int getCleanupTimeoutMillis() { return cleanupTimeoutMillis; } public void setCleanupTimeoutMillis(final int cleanupTimeoutMillis) { this.cleanupTimeoutMillis = cleanupTimeoutMillis; } public int getUploadSizeLimitBytes() { return uploadSizeLimitBytes; } public void setUploadSizeLimitBytes(final int uploadSizeLimitBytes) { this.uploadSizeLimitBytes = uploadSizeLimitBytes; } private void log(final Level level, final String message) { if (logger.isLoggable(level)) { logger.log(level, message); } } }