418 lines
14 KiB
Java
418 lines
14 KiB
Java
package org.gcube.portlets.user.shareupdates.server;
|
|
|
|
import java.awt.Dimension;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.Image;
|
|
import java.awt.Rectangle;
|
|
import java.awt.image.BufferedImage;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.RandomAccessFile;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URL;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.channels.FileChannel;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Iterator;
|
|
import java.util.UUID;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.imageio.ImageReader;
|
|
import javax.imageio.stream.ImageInputStream;
|
|
import javax.servlet.ServletContext;
|
|
|
|
import net.coobird.thumbnailator.Thumbnails;
|
|
|
|
import org.apache.commons.fileupload.util.Streams;
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
import org.apache.pdfbox.util.PDFTextStripper;
|
|
import org.apache.tika.config.TikaConfig;
|
|
import org.apache.tika.detect.Detector;
|
|
import org.apache.tika.io.TikaInputStream;
|
|
import org.apache.tika.metadata.Metadata;
|
|
import org.apache.tika.mime.MediaType;
|
|
import org.gcube.contentmanagement.blobstorage.service.IClient;
|
|
import org.gcube.portlets.user.shareupdates.shared.LinkPreview;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.sun.pdfview.PDFFile;
|
|
import com.sun.pdfview.PDFPage;
|
|
import com.sun.pdfview.PDFParseException;
|
|
/**
|
|
* Parse files and returns an image preview plus description
|
|
* @author Massimiliano Assante, ISTI-CNR
|
|
*/
|
|
public class FilePreviewer {
|
|
|
|
private static final Logger _log = LoggerFactory.getLogger(FilePreviewer.class);
|
|
private static final String UPLOAD_LOCATION_LOCAL = System.getProperty("java.io.tmpdir");
|
|
public static final String DIR_STORAGE_DEFAULT_ICONS = "default-icons-directory"; // to be append to UPLOAD_DIR
|
|
|
|
/**
|
|
* these are the extension for which I have an icon image preview
|
|
*/
|
|
private static final String[] handledextensionImages = {"css", "csv", "doc", "docx", "java", "mdb", "mp3", "pdf", "ppt", "pptx", "psd", "rar", "tex", "txt", "xls", "xlsx", "zip"};
|
|
|
|
/**
|
|
*
|
|
* @param fileNameLabel thename of the file
|
|
* @param path2Pdf the path of the pdf file
|
|
* @param httpUrl the http url where the file is reachable at
|
|
* @param storageClient
|
|
* @return
|
|
* @throws Exception
|
|
*/
|
|
protected static LinkPreview getPdfPreview(
|
|
String fileName,
|
|
String path2Pdf,
|
|
String httpUrl,
|
|
String mimeType,
|
|
IClient sClient,
|
|
ServletContext sContext) throws Exception {
|
|
ArrayList<String> imagesUrl = new ArrayList<String>();
|
|
//description
|
|
String desc = null;
|
|
try {
|
|
desc = getPDFDescription(path2Pdf);
|
|
}
|
|
catch (Exception ex) {
|
|
_log.warn("PDF Parse exception, returning no description");
|
|
desc = "";
|
|
}
|
|
//thumbnail preview
|
|
File pdfFile = new File(path2Pdf);
|
|
|
|
RandomAccessFile raf = new RandomAccessFile(pdfFile, "r");
|
|
FileChannel channel = raf.getChannel();
|
|
ByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
|
|
PDFFile pdf = null;
|
|
try {
|
|
pdf = new PDFFile(buf);
|
|
} catch (PDFParseException ex) {
|
|
raf.close();
|
|
_log.error("PDF Parse exception, returning default pdf image");
|
|
imagesUrl.add(getDefaultIconUrl(sClient, IconFileNames.PDF.getFileName(), sContext));
|
|
return new LinkPreview(fileName, desc, httpUrl, mimeType, imagesUrl);
|
|
}
|
|
PDFPage page = pdf.getPage(0);
|
|
|
|
int width = (int) page.getBBox().getWidth();
|
|
int height = (int) page.getBBox().getHeight();
|
|
|
|
int scaledWidth = width/8;
|
|
int scaledHeight = height/8;
|
|
|
|
// create the image
|
|
Rectangle rect = new Rectangle(0, 0, width, height);
|
|
BufferedImage bufferedImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
|
|
Image image = page.getImage(scaledWidth, scaledHeight, rect, null, true, true);
|
|
Graphics2D bufImageGraphics = bufferedImage.createGraphics();
|
|
bufImageGraphics.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
|
|
|
|
//thumbnail previes are very small in this case we can use in-memory streams
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
boolean result = ImageIO.write(bufferedImage, "JPG", out);
|
|
raf.close();
|
|
|
|
if (result) {
|
|
try{
|
|
String storagehttpLink = syncUploadThumbnailStorage(new ByteArrayInputStream(out.toByteArray()), sClient, "thumbnail_" + fileName);
|
|
imagesUrl.add(storagehttpLink);
|
|
_log.debug("PDF thumbnail available at: " + storagehttpLink);
|
|
}catch(Exception e){
|
|
_log.error("Failed to create a thumbnail for the preview ...");
|
|
imagesUrl.add(getDefaultIconUrl(sClient, IconFileNames.PDF.getFileName(), sContext));
|
|
}
|
|
return new LinkPreview(fileName, desc, httpUrl, mimeType, imagesUrl);
|
|
}
|
|
else
|
|
throw new IOException("Could not process pdf file");
|
|
}
|
|
/**
|
|
*
|
|
* @param fileNameLabel thename of the file
|
|
* @param path2Image the path of the image file
|
|
* @param httpUrl the http url where the file is reachable at
|
|
* @return
|
|
* @throws Exception
|
|
*/
|
|
protected static LinkPreview getImagePreview(String fileName, String path2Image, String httpUrl, String mimeType, IClient sClient, ServletContext sContext) {
|
|
ArrayList<String> imagesUrl = new ArrayList<String>();
|
|
|
|
Dimension dim;
|
|
ByteArrayOutputStream out = null;
|
|
String desc = "";
|
|
try {
|
|
dim = extractDimension(path2Image);
|
|
|
|
//description
|
|
desc = ((int) dim.getWidth()) + "x" + ((int) dim.getHeight()) + " pixels";
|
|
|
|
out = new ByteArrayOutputStream();
|
|
|
|
Thumbnails.of(path2Image)
|
|
.width(80)
|
|
.outputFormat("jpg")
|
|
.toOutputStream(out);
|
|
|
|
String storagehttpLink = syncUploadThumbnailStorage(new ByteArrayInputStream(out.toByteArray()), sClient, "thumbnail_" + fileName);
|
|
_log.debug("Image thumbnail available at: " + storagehttpLink);
|
|
imagesUrl.add(storagehttpLink);
|
|
}catch (IOException e) {
|
|
_log.error("Failed to get a preview... Using generic image");
|
|
imagesUrl.add(getDefaultIconUrl(sClient, IconFileNames.IMAGE_GENERIC.getFileName(), sContext));
|
|
}
|
|
|
|
return new LinkPreview(fileName, desc, httpUrl, mimeType, imagesUrl);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param fileNameLabel thename of the file
|
|
* @param path2Pdf the path of the pdf file
|
|
* @param httpUrl the http url where the file is reachable at
|
|
* @return
|
|
* @throws Exception
|
|
*/
|
|
protected static LinkPreview getUnhandledTypePreview(
|
|
String fileName,
|
|
String path2Pdf,
|
|
String httpUrl,
|
|
String mimeType,
|
|
IClient sClient,
|
|
ServletContext sContext) throws Exception {
|
|
|
|
ArrayList<String> imagesUrl = new ArrayList<String>();
|
|
String extension = getExtension(fileName);
|
|
//no description
|
|
String desc = "";
|
|
if (extension == null)
|
|
imagesUrl.add(getDefaultIconUrl(sClient, IconFileNames.GENERIC.getFileName(), sContext));
|
|
else {
|
|
int foundIndex = Arrays.binarySearch(handledextensionImages, extension);
|
|
if (foundIndex < 0)
|
|
imagesUrl.add(getDefaultIconUrl(sClient, IconFileNames.GENERIC.getFileName(), sContext));
|
|
else
|
|
imagesUrl.add(getDefaultIconUrl(sClient, extension+".png", sContext));
|
|
}
|
|
return new LinkPreview(fileName, desc, httpUrl, mimeType, imagesUrl);
|
|
}
|
|
|
|
private static String getExtension(String fileName) {
|
|
int lastDot = fileName.lastIndexOf(".");
|
|
String extension = fileName.substring(lastDot+1);
|
|
_log.debug("EXTENSION FOUND = " + extension);
|
|
return extension;
|
|
}
|
|
/**
|
|
*
|
|
* @param path2File
|
|
* @return
|
|
* @throws Exception
|
|
*/
|
|
private static String getPDFDescription(String path2File) throws Exception {
|
|
PDDocument doc = PDDocument.load(path2File);
|
|
PDFTextStripper stripper = new PDFTextStripper();
|
|
//only first page text
|
|
stripper.setStartPage(1);
|
|
stripper.setEndPage(1);
|
|
String text = stripper.getText(doc);
|
|
String toReturn = (text.length() > 300) ? text.substring(0, 295) + " ... " : text;
|
|
doc.close();
|
|
return toReturn;
|
|
}
|
|
/**
|
|
* extract the dimension in pixels without reading the whole file
|
|
* @param path2Image
|
|
* @return
|
|
* @throws IOException
|
|
*/
|
|
private static Dimension extractDimension(String path2Image) throws IOException {
|
|
ImageInputStream in = ImageIO.createImageInputStream(new File(path2Image));
|
|
try {
|
|
final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
|
|
if (readers.hasNext()) {
|
|
ImageReader reader = readers.next();
|
|
try {
|
|
reader.setInput(in);
|
|
return new Dimension(reader.getWidth(0), reader.getHeight(0));
|
|
} finally {
|
|
reader.dispose();
|
|
}
|
|
}
|
|
} finally {
|
|
if (in != null) in.close();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param file
|
|
* @return
|
|
* @throws IOException
|
|
* @throws MagicParseException
|
|
* @throws MagicMatchNotFoundException
|
|
* @throws MagicException
|
|
*/
|
|
protected static String getMimeType(File file, String filenameWithExtension) throws IOException {
|
|
TikaConfig config = TikaConfig.getDefaultConfig();
|
|
Detector detector = config.getDetector();
|
|
TikaInputStream stream = TikaInputStream.get(file);
|
|
Metadata metadata = new Metadata();
|
|
metadata.add(Metadata.RESOURCE_NAME_KEY, filenameWithExtension);
|
|
MediaType mediaType = detector.detect(stream, metadata);
|
|
return mediaType.getBaseType().toString();
|
|
}
|
|
|
|
/**
|
|
* Same as above, but having as input an inputstream object
|
|
* @param is
|
|
* @param filenameWithExtension
|
|
* @return
|
|
* @throws IOException
|
|
*/
|
|
protected static String getMimeType(InputStream is, String filenameWithExtension) throws IOException {
|
|
TikaConfig config = TikaConfig.getDefaultConfig();
|
|
Detector detector = config.getDetector();
|
|
TikaInputStream stream = TikaInputStream.get(is);
|
|
Metadata metadata = new Metadata();
|
|
metadata.add(Metadata.RESOURCE_NAME_KEY, filenameWithExtension);
|
|
MediaType mediaType = detector.detect(stream, metadata);
|
|
return mediaType.getBaseType().toString();
|
|
}
|
|
|
|
/**
|
|
* Upload a thumbnail on the storage and returns the http url. The operation is synchronous
|
|
* @param sClient
|
|
* @param byteArrayInputStream
|
|
* @return http url of the thumbnail
|
|
*/
|
|
protected static String syncUploadThumbnailStorage(ByteArrayInputStream byteArrayInputStream, IClient sClient, String thumbnailFileName){
|
|
try{
|
|
// a random remote folder/file on the storage for the thumbnail
|
|
String randomUploadFolderName = UUID.randomUUID().toString();
|
|
String remoteFilePath = ShareUpdateServiceImpl.UPLOAD_DIR + "/" + randomUploadFolderName + "/" + thumbnailFileName;
|
|
String thumbnailMime = getMimeType(byteArrayInputStream, null);
|
|
sClient.put(true, thumbnailMime).LFile(byteArrayInputStream).RFile(remoteFilePath);
|
|
return sClient.getHttpUrl(true).RFile(remoteFilePath);
|
|
}catch(Exception e){
|
|
_log.error("Failed to upload thumbnail on storage...");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a resource at a given url exists or not. If it exists, create a file in the temp dir
|
|
* of the tomcat. Files and folers are deleted on exit.
|
|
* @param urlThumbnail
|
|
* @return
|
|
*/
|
|
public static File storeAndGetFile(String urlThumbnail){
|
|
try {
|
|
HttpURLConnection.setFollowRedirects(true);
|
|
HttpURLConnection con =
|
|
(HttpURLConnection) new URL(urlThumbnail).openConnection();
|
|
con.setRequestMethod("HEAD"); // body is not needed yet
|
|
if(con.getResponseCode() == HttpURLConnection.HTTP_OK){
|
|
|
|
//generate the random dir
|
|
File theRandomDir = new File(UPLOAD_LOCATION_LOCAL + File.separator + UUID.randomUUID().toString());
|
|
theRandomDir.mkdir();
|
|
theRandomDir.deleteOnExit();
|
|
_log.debug("Created temp upload directory in: " + theRandomDir);
|
|
|
|
// generate a random file name and create it under the randomDir
|
|
File file = new File(theRandomDir, UUID.randomUUID().toString().substring(0, 10));
|
|
file.deleteOnExit();
|
|
|
|
//Get the inputstream and copy there
|
|
URL url = new URL(urlThumbnail);
|
|
Streams.copy(url.openStream(), new FileOutputStream(file), true);
|
|
_log.debug("File is at " + file.getAbsolutePath());
|
|
|
|
return file;
|
|
}else
|
|
return null;
|
|
}
|
|
catch (Exception e) {
|
|
_log.error("The resource at url " + urlThumbnail + " doesn't exist");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When a link preview needs to be saved, the method save the thumbnail on the storage no longer using the external url
|
|
* @param urlThumbnail
|
|
* @return the url of the thumbnail saved on the storage or null in case of error
|
|
*/
|
|
public static String getThumbnailFromUrl(String urlThumbnail, IClient sClient, ServletContext sContext) {
|
|
File localFile;
|
|
if((localFile = storeAndGetFile(urlThumbnail)) != null){
|
|
String mimeType = null;
|
|
try {
|
|
mimeType = FilePreviewer.getMimeType(localFile, localFile.getName());
|
|
String thumbnailUrlStorage = null;
|
|
switch(mimeType){
|
|
case "image/png":
|
|
case "image/gif":
|
|
case "image/tiff":
|
|
case "image/jpg":
|
|
case "image/jpeg":
|
|
case "image/bmp":
|
|
thumbnailUrlStorage = getImagePreview(localFile.getName(), localFile.getAbsolutePath(), null, mimeType, sClient, sContext).getImageUrls().get(0);
|
|
break;
|
|
default: break;
|
|
}
|
|
return thumbnailUrlStorage;
|
|
} catch (IOException e) {
|
|
_log.error("Error while saving thumbnail on ftp", e);
|
|
}
|
|
|
|
}else
|
|
_log.warn("the file at url " + urlThumbnail + " doesn't exist");
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* When the preview methods fail to create a thumbnail and one of the "default icons" needs to be used,
|
|
* the method retrieve the url of these files from the storage. In case the icon is missing, it upload the file too on the storage
|
|
* before returning.
|
|
* @param storage
|
|
* @return
|
|
*/
|
|
private static String getDefaultIconUrl(IClient sClient, String iconFile, ServletContext sContext){
|
|
|
|
try{
|
|
//TODO do I need to download the whole stream?!
|
|
String iconPathOnStorage = ShareUpdateServiceImpl.UPLOAD_DIR + "/" + FilePreviewer.DIR_STORAGE_DEFAULT_ICONS + "/" + iconFile;
|
|
_log.debug("looking for remote file at relative url " + iconPathOnStorage);
|
|
|
|
InputStream inputStream = sClient.get().RFileAsInputStream(iconPathOnStorage);
|
|
if(inputStream == null){
|
|
_log.info("Ok, the default icon was not on the storage.. going to put it there");
|
|
InputStream localInputStream = sContext.getResourceAsStream("/images/default/" + iconFile);
|
|
if(localInputStream != null){
|
|
sClient.put(true).LFile(localInputStream).RFile(iconPathOnStorage);
|
|
_log.info("Request for upload started for file " + iconPathOnStorage + ", trying to get the http url");
|
|
return sClient.getHttpUrl(true).RFile(iconPathOnStorage);
|
|
}else{
|
|
_log.warn("Failed to get the inputstream of the icon " + iconFile + " form the subpath /images/default/");
|
|
return null;
|
|
}
|
|
}else
|
|
return sClient.getHttpUrl(true).RFile(iconPathOnStorage);
|
|
}catch(Exception e){
|
|
_log.error("Something went wrong with the storage for the default image.. returning null", e);
|
|
}
|
|
return null;
|
|
}
|
|
}
|