/** * Copyright (C) 2011 JTalks.org Team * This library 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. * This library 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 */ package org.jtalks.jcommune.service.nontransactional; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.Validate; import org.apache.tika.Tika; import org.jtalks.jcommune.model.entity.JCommuneProperty; import org.jtalks.jcommune.service.exceptions.ImageFormatException; import org.jtalks.jcommune.service.exceptions.ImageProcessException; import org.jtalks.jcommune.service.exceptions.ImageSizeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.web.multipart.MultipartFile; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; /** * Service class for uploaded image related operations * * @author Alexandre Teterin * @author Andrei Alikov */ public class ImageService { public static final String ICO_TYPE = "image/x-icon"; private static final List<String> VALID_IMAGE_TYPES = Arrays.asList("image/jpeg", "image/png", "image/gif", ICO_TYPE); /** * user-friendly string with all valid image types */ private static final String VALID_IMAGE_EXTENSIONS = "*.jpeg, *.jpg, *.gif, *.png, *.ico"; private ImageConverter imageConverter; private Base64Wrapper base64Wrapper; private JCommuneProperty imageSizeProperty; private String defaultImagePath; private static final Logger LOGGER = LoggerFactory.getLogger(ImageService.class); /** * Create ImageService instance * * @param imageConverter object for image pre processing * @param base64Wrapper to encode/decode image passed from the client side * @param defaultImagePath class path to load default image * @param imageSizeProperty let us know the limitation of image max size */ public ImageService( ImageConverter imageConverter, Base64Wrapper base64Wrapper, String defaultImagePath, JCommuneProperty imageSizeProperty) { this.imageConverter = imageConverter; this.base64Wrapper = base64Wrapper; this.imageSizeProperty = imageSizeProperty; this.defaultImagePath = defaultImagePath; } /** * Returns default image to be used when custom image is not set * * @return byte array-stored image */ public byte[] getDefaultImage() { byte[] result; try { result = getFileBytes(defaultImagePath); } catch (IOException e) { result = new byte[0]; LOGGER.error("Failed to load default image", e); } return result; } /** * Pre process image to fit maximum size and be in the target format and * convert the contents of the result image into String64 format * * @param bytes image for conversion * @return result string64 format * @throws org.jtalks.jcommune.service.exceptions.ImageProcessException * common image processing error */ public String preProcessAndEncodeInString64(byte[] bytes) throws ImageProcessException { Validate.notNull(bytes, "Incoming byte array cannot be null"); BufferedImage image = imageConverter.convertByteArrayToImage(bytes); if (image == null) { // something went wrong during conversion throw new ImageProcessException(); } byte[] outputImage = imageConverter.preprocessImage(image); return base64Wrapper.encodeB64Bytes(outputImage); } /** * Validate file format * * @param file for validation, cannot be null * @throws org.jtalks.jcommune.service.exceptions.ImageFormatException * invalid format image processing error */ public void validateImageFormat(MultipartFile file) throws ImageFormatException { Validate.notNull(file, "file argument array cannot be null"); if (!VALID_IMAGE_TYPES.contains(file.getContentType())) { LOGGER.debug("Wrong file extension. May be only {}", VALID_IMAGE_EXTENSIONS); throw new ImageFormatException(VALID_IMAGE_EXTENSIONS); } } /** * Image byte array data format * * @param bytes for validation * @throws ImageFormatException invalid format image processing error */ public void validateImageFormat(byte[] bytes) throws ImageFormatException { Validate.notNull(bytes, "Incoming byte array cannot be null"); Tika tika = new Tika(); InputStream input = new ByteArrayInputStream(bytes); try { String type = tika.detect(input); if (!VALID_IMAGE_TYPES.contains(type)) { LOGGER.debug("Wrong file extension. May be only {}", VALID_IMAGE_EXTENSIONS); throw new ImageFormatException(VALID_IMAGE_EXTENSIONS); } } catch (IOException e) { LOGGER.error("Failed to handle image ByteArrayInputStream", e); } } /** * Validate image size * * @param bytes array for validation * @throws org.jtalks.jcommune.service.exceptions.ImageSizeException * invalid size image processing error */ public void validateImageSize(byte[] bytes) throws ImageSizeException { Validate.notNull(bytes, "Incoming byte array cannot be null"); int maxSize = imageSizeProperty.intValue(); if (bytes.length > maxSize) { LOGGER.debug("File has too big size. Must be less than {} bytes", maxSize); throw new ImageSizeException(maxSize); } } /** * Gets content of the file by its classpath * * @param classPath classpath of the file to be loaded * @return content of the loaded file * @throws IOException */ private byte[] getFileBytes(String classPath) throws IOException { byte[] result; ClassPathResource fileClassPathSource = new ClassPathResource(classPath); InputStream stream = null; try { stream = fileClassPathSource.getInputStream(); result = new byte[stream.available()]; Validate.isTrue(stream.read(result) > 0); } finally { IOUtils.closeQuietly(stream); } return result; } /** * Gets prefix for "src" attribute of the "img" tag representing the image format * * @return prefix for "src" attribute of the "img" tag representing the image format */ public String getHtmlSrcImagePrefix() { return imageConverter.getHtmlSrcImagePrefix(); } }