Java tutorial
/** * Copyright (C) 2016 Stichting PALGA * This file is distributed under the GNU Affero General Public License * (see accompanying file <a href="{@docRoot}/LICENSE">LICENSE</a>). */ package business.services; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import javax.annotation.PostConstruct; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import business.exceptions.FileCopyError; import business.exceptions.FileDeleteError; import business.exceptions.FileDownloadError; import business.exceptions.FileNotFound; import business.exceptions.FileUploadError; import business.models.File; import business.models.FileRepository; import business.models.User; @Service public class FileService { Log log = LogFactory.getLog(getClass()); FileSystem fileSystem = FileSystems.getDefault(); @Autowired FileRepository fileRepository; @Value("${dntp.upload-path}") String uploadPath; private static String accessLogsPath = "./logs/"; @PostConstruct public void init() throws IOException { Path path = fileSystem.getPath(uploadPath).normalize(); if (!path.toFile().exists()) { Files.createDirectory(path); } log.info("File upload path: " + path.toAbsolutePath()); } public static String getBasename(String name) { String[] tokens = name.split("\\.(?=[^\\.]+$)"); if (tokens.length > 0) { return tokens[0]; } else { return ""; } } public static String getExtension(String name) { String[] tokens = name.split("\\.(?=[^\\.]+$)"); if (tokens.length > 1) { return "." + tokens[1]; } else { return ""; } } Map<String, SortedMap<Integer, Path>> uploadChunks = new HashMap<String, SortedMap<Integer, Path>>(); public File uploadPart(User user, String name, File.AttachmentType type, MultipartFile file, Integer chunk, Integer chunks, String flowIdentifier) { try { String identifier = user.getId().toString() + "_" + flowIdentifier; String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; log.info("File content-type: " + file.getContentType()); try { contentType = MediaType.valueOf(file.getContentType()).toString(); log.info("Media type: " + contentType); } catch (InvalidMediaTypeException e) { log.warn("Invalid content type: " + e.getMediaType()); //throw new FileUploadError("Invalid content type: " + e.getMediaType()); } InputStream input = file.getInputStream(); // Create temporary file for chunk Path path = fileSystem.getPath(uploadPath).normalize(); if (!path.toFile().exists()) { Files.createDirectory(path); } name = URLEncoder.encode(name, "utf-8"); String prefix = getBasename(name); String suffix = getExtension(name); Path f = Files.createTempFile(path, prefix, suffix + "." + chunk + ".chunk").normalize(); // filter path names that point to places outside the upload path. // E.g., to prevent that in cases where clients use '../' in the filename // arbitrary locations are reachable. if (!Files.isSameFile(path, f.getParent())) { // Path f is not in the upload path. Maybe 'name' contains '..'? throw new FileUploadError("Invalid file name"); } log.info("Copying file to " + f.toString()); // Copy chunk to temporary file Files.copy(input, f, StandardCopyOption.REPLACE_EXISTING); // Save chunk location in chunk map SortedMap<Integer, Path> chunkMap; synchronized (uploadChunks) { // FIXME: perhaps use a better identifier? Not sure if this one // is unique enough... chunkMap = uploadChunks.get(identifier); if (chunkMap == null) { chunkMap = new TreeMap<Integer, Path>(); uploadChunks.put(identifier, chunkMap); } } chunkMap.put(chunk, f); log.info("Chunk " + chunk + " saved to " + f.toString()); // Assemble complete file if all chunks have been received if (chunkMap.size() == chunks.intValue()) { uploadChunks.remove(identifier); Path assembly = Files.createTempFile(path, prefix, suffix).normalize(); // filter path names that point to places outside the upload path. // E.g., to prevent that in cases where clients use '../' in the filename // arbitrary locations are reachable. if (!Files.isSameFile(path, assembly.getParent())) { // Path assembly is not in the upload path. Maybe 'name' contains '..'? throw new FileUploadError("Invalid file name"); } log.info("Assembling file " + assembly.toString() + " from " + chunks + " chunks..."); OutputStream out = Files.newOutputStream(assembly, StandardOpenOption.CREATE, StandardOpenOption.APPEND); // Copy chunks to assembly file, delete chunk files for (int i = 1; i <= chunks; i++) { //log.info("Copying chunk " + i + "..."); Path source = chunkMap.get(new Integer(i)); if (source == null) { log.error("Cannot find chunk " + i); throw new FileUploadError("Cannot find chunk " + i); } Files.copy(source, out); Files.delete(source); } // Save assembled file name to database log.info("Saving attachment to database..."); File attachment = new File(); attachment.setName(URLDecoder.decode(name, "utf-8")); attachment.setType(type); attachment.setMimeType(contentType); attachment.setDate(new Date()); attachment.setUploader(user); attachment.setFilename(assembly.getFileName().toString()); attachment = fileRepository.save(attachment); return attachment; } return null; } catch (IOException e) { log.error(e); throw new FileUploadError(e.getMessage()); } } public InputStream getInputStream(File attachment) { if (attachment == null) { throw new FileNotFound(); } try { FileSystem fileSystem = FileSystems.getDefault(); Path path = fileSystem.getPath(uploadPath, attachment.getFilename()); InputStream input = new FileInputStream(path.toFile()); return input; } catch (IOException e) { log.error(e); throw new FileDownloadError(); } } public HttpEntity<InputStreamResource> download(Long id) { try { File attachment = fileRepository.findOne(id); if (attachment == null) { throw new FileNotFound(); } FileSystem fileSystem = FileSystems.getDefault(); Path path = fileSystem.getPath(uploadPath, attachment.getFilename()); InputStream input = new FileInputStream(path.toFile()); InputStreamResource resource = new InputStreamResource(input); HttpHeaders headers = new HttpHeaders(); if (attachment.getMimeType() != null) { headers.setContentType(MediaType.valueOf(attachment.getMimeType())); } else { headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); } headers.set("Content-Disposition", "attachment; filename=" + attachment.getName().replace(" ", "_")); HttpEntity<InputStreamResource> response = new HttpEntity<InputStreamResource>(resource, headers); return response; } catch (IOException e) { log.error(e); throw new FileDownloadError(); } } public File save(File file) { return fileRepository.save(file); } public File clone(File file) { try { FileSystem fileSystem = FileSystems.getDefault(); // source Path source = fileSystem.getPath(uploadPath, file.getFilename()); // target Path path = fileSystem.getPath(uploadPath).normalize(); String prefix = getBasename(file.getFilename()); String suffix = getExtension(file.getFilename()); Path target = Files.createTempFile(path, prefix, suffix).normalize(); // copy log.info("Copying " + source + " to " + target + " ..."); Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); // save clone File result = file.clone(); result.setFilename(target.getFileName().toString()); return save(result); } catch (IOException e) { log.error(e); throw new FileCopyError(); } } public void removeAttachment(File attachment) { fileRepository.delete(attachment); Path path = fileSystem.getPath(uploadPath, attachment.getFilename()); try { Files.delete(path); } catch (IOException e) { log.error(e); throw new FileDeleteError(); } } public boolean checkUploadPath() { Path path = fileSystem.getPath(uploadPath).normalize(); java.io.File f = path.toFile(); if (f.exists() && f.isDirectory() && f.canWrite()) { return true; } return false; } public List<String> getAccessLogFilenames() { try { List<String> logFiles = new ArrayList<String>(); for (Path p : Files.newDirectoryStream(fileSystem.getPath("./logs/"), "dntp-access*.log")) { logFiles.add(p.getFileName().toString()); } Collections.sort(logFiles, Collections.reverseOrder()); return logFiles; } catch (IOException e) { log.error(e); throw new FileDownloadError(); } } public HttpEntity<InputStreamResource> downloadAccessLog(String filename, boolean writeContentDispositionHeader) { try { FileSystem fileSystem = FileSystems.getDefault(); Path path = fileSystem.getPath(accessLogsPath).normalize(); filename = filename.replace(fileSystem.getSeparator(), "_"); filename = URLEncoder.encode(filename, "utf-8"); Path f = fileSystem.getPath(accessLogsPath, filename).normalize(); // filter path names that point to places outside the logs path. // E.g., to prevent that in cases where clients use '../' in the filename // arbitrary locations are reachable. if (!Files.isSameFile(path, f.getParent())) { // Path f is not in the upload path. Maybe 'name' contains '..'? log.error("Invalid filename: " + filename); throw new FileDownloadError("Invalid file name"); } if (!Files.isReadable(f)) { log.error("File does not exist: " + filename); throw new FileDownloadError("File does not exist"); } InputStream input = new FileInputStream(f.toFile()); InputStreamResource resource = new InputStreamResource(input); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.TEXT_PLAIN); if (writeContentDispositionHeader) { headers.set("Content-Disposition", "attachment; filename=" + filename.replace(" ", "_")); } HttpEntity<InputStreamResource> response = new HttpEntity<InputStreamResource>(resource, headers); return response; } catch (IOException e) { log.error(e); throw new FileDownloadError(); } } }