package com.aemreunal.helper; /* * *********************** * * Copyright (c) 2015 * * * * This code belongs to: * * * * @author Ahmet Emre Ünal * * S001974 * * * * aemreunal@gmail.com * * emre.unal@ozu.edu.tr * * * * aemreunal.com * * *********************** * */ import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.util.UUID; import javax.imageio.ImageIO; import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; import com.aemreunal.config.GlobalSettings; import com.aemreunal.exception.imageStorage.ImageDeleteException; import com.aemreunal.exception.imageStorage.ImageLoadException; import com.aemreunal.exception.imageStorage.ImageSaveException; import com.aemreunal.exception.region.MultipartFileReadException; import com.aemreunal.exception.region.WrongFileTypeSubmittedException; public class ImageStorage { /** * Saves the given image to the filesystem and returns the properties of the saved * image file as an {@link ImageProperties ImageProperties} object. * <p> * The method saves images under the home folder of the user, inside the:<pre> * ~/{@value com.aemreunal.config.GlobalSettings#ROOT_STORAGE_FOLDER_DIRECTORY_NAME}/{@value * com.aemreunal.config.GlobalSettings#IMAGE_STORAGE_FOLDER_DIRECTORY_NAME}/</pre> * folder. The regular region images will be put in a sub-folder structure like:<pre> * <project ID>/<region ID>/</pre> under the main * storage folder. Inter-region navigation images will be put directly under * the:<pre> * <user name>/<project ID>/</pre> folder, without any region-specific * foldering. * <p> * If any of the folders above do not exist, they will be created. * * @param projectId * The ID of the project which the region (the image belongs to) is a part * of. * @param regionId * The ID of the region of the image. When saving an inter-region navigation * connection image, this value should be {@code null}. * @param imageMultipartFile * The image as a {@link MultipartFile MultipartFile}. * * @return The properties of the saved image file as an {@link ImageProperties * ImageProperties} object. These properties are: <ul> <li>Image name</li> <li>Image * width</li> <li>Image height</li> </ul> * * @throws MultipartFileReadException * If the {@link MultipartFile Multipart file} can't be read. * @throws ImageSaveException * If the image can't be saved. * @throws WrongFileTypeSubmittedException * If the submitted {@link MultipartFile Multipart file} is of a wrong type * (e.g. non-image file, unacceptable image type). */ public ImageProperties saveImage(Long projectId, Long regionId, MultipartFile imageMultipartFile) throws MultipartFileReadException, ImageSaveException, WrongFileTypeSubmittedException { // Verify file is not empty or is not of a wrong type verifyImageType(projectId, regionId, imageMultipartFile); // Get the file path from the project ID and region ID attributes String filePath = getFilePath(projectId, regionId); // Ensure unique file name File imageFile = getUniqueFile(filePath); // Check for the existence of the parent folder and create it // if it doesn't exist createParentFolder(projectId, regionId, imageFile); // Create the new image file createFile(projectId, regionId, imageFile); // Write the image bytes to the new file writeImageToFile(projectId, regionId, imageMultipartFile, imageFile); // Read the image properties to get dimensions return readImageProperties(projectId, regionId, imageFile); } /** * Loads the specified image file and returns the bytes of the image file in a * <code>byte[]</code>. * * @param projectId * The ID of the project which the region (the image belongs to) is a part * of. * @param regionId * The ID of the region of the image. When saving an inter-region navigation * connection image, this value should be {@code null}. * @param imageFileName * The name of the image file to be loaded. * * @return A <code>byte[]</code> if the image file has been successfully loaded and * read, <code>null</code> otherwise. * * @throws ImageLoadException * If the image can't be loaded. */ public byte[] loadImage(Long projectId, Long regionId, String imageFileName) throws ImageLoadException { // Get the file path from the project ID and region ID attributes String filePath = getFilePath(projectId, regionId); // Get the image file File imageFile = new File(filePath + imageFileName); if (!imageFile.exists()) { System.err.println("Image file does not exist!"); throw new ImageLoadException(projectId, regionId); } return loadImageFromFile(projectId, regionId, imageFile); } /** * Deletes the specified image file. The method will do nothing if a {@code null} * value is provided for {@code imageFileName} parameter. * * @param projectId * The ID of the project which the region (the image belongs to) is a part * of. * @param regionId * The ID of the region of the image. * @param imageFileName * The name of the image file to be deleted. If a {@code null} value is * provided, the method will do nothing and return. * * @throws ImageDeleteException * If the image file can't be deleted. */ public void deleteImage(Long projectId, Long regionId, String imageFileName) throws ImageDeleteException { if (imageFileName == null || imageFileName.equals("")) { return; } // Get the file path from the project ID and region ID attributes String filePath = getFilePath(projectId, regionId); // Get the image file File imageFile = new File(filePath + imageFileName); try { Files.delete(imageFile.toPath()); } catch (NoSuchFileException e) { GlobalSettings.err("WARNING: Image file for project: " + projectId + ", region " + regionId + ", file name: " + imageFileName + " does not exist, nothing to delete!"); } catch (IOException e) { GlobalSettings.err("Unable to delete the image!"); throw new ImageDeleteException(projectId, regionId); } } private void verifyImageType(Long projectId, Long regionId, MultipartFile imageMultipartFile) throws WrongFileTypeSubmittedException { if (imageMultipartFile.isEmpty() || !fileTypeIsImage(imageMultipartFile)) { throw new WrongFileTypeSubmittedException(projectId, regionId); } } private boolean fileTypeIsImage(MultipartFile file) { String type = file.getContentType(); return type.equalsIgnoreCase(MediaType.IMAGE_JPEG_VALUE); } private File getUniqueFile(String filePath) { File imageFile; do { String fileName = UUID.randomUUID().toString(); imageFile = new File(filePath + fileName); } while (imageFile.exists()); return imageFile; } private void createParentFolder(Long projectId, Long regionId, File imageFile) throws ImageSaveException { if (!imageFile.getParentFile().exists()) { // If it doesn't exist, create it if (!imageFile.getParentFile().mkdirs()) { System.err.println("Unable to create parent folders!"); throw new ImageSaveException(projectId, regionId); } } } private void createFile(Long projectId, Long regionId, File imageFile) throws ImageSaveException { try { if (!imageFile.createNewFile()) { System.err.println("Unable to createNewFile()!"); throw new ImageSaveException(projectId, regionId); } } catch (IOException e) { System.err.println("Unable to create file!"); throw new ImageSaveException(projectId, regionId); } } private void writeImageToFile(Long projectId, Long regionId, MultipartFile imageMultipartFile, File imageFile) throws ImageSaveException { try { imageMultipartFile.transferTo(imageFile); } catch (IOException e) { System.err.println("Unable to write image to file!"); throw new ImageSaveException(projectId, regionId); } } private ImageProperties readImageProperties(Long projectId, Long regionId, File imageFile) throws ImageSaveException { try { BufferedImage image = ImageIO.read(imageFile); return new ImageProperties(imageFile.getName(), image.getWidth(), image.getHeight()); } catch (IOException e) { System.err.println("Unable to read image to get dimensions!"); throw new ImageSaveException(projectId, regionId); } } private byte[] loadImageFromFile(Long projectId, Long regionId, File imageFile) throws ImageLoadException { // Can safely cast from long to int, as image file will never exceed // 2^32 bytes (which would've required the use of a 64 bit long). byte[] imageAsBytes = new byte[(int) imageFile.length()]; try { FileInputStream stream = new FileInputStream(imageFile); stream.read(imageAsBytes); stream.close(); } catch (FileNotFoundException e) { System.err.println("File to read from is not found!"); throw new ImageLoadException(projectId, regionId); } catch (IOException e) { System.err.println("Unable to read from file!"); throw new ImageLoadException(projectId, regionId); } return imageAsBytes; } private String getFilePath(Long projectId, Long regionId) { if (regionId != null) { return GlobalSettings.IMAGE_STORAGE_FOLDER_PATH + projectId.toString() + "/" + regionId.toString() + "/"; } else { return GlobalSettings.IMAGE_STORAGE_FOLDER_PATH + projectId.toString() + "/"; } } }