/*
* ====================================================================
* Copyright (c) 2005-2012 sventon project. All rights reserved.
*
* This software is licensed as described in the file LICENSE, which
* you should have received as part of this distribution. The terms
* are also available at http://www.sventon.org.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.sventon.web.ctrl.template;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.sventon.SVNConnection;
import org.sventon.cache.ObjectCacheManager;
import org.sventon.cache.objectcache.ObjectCache;
import org.sventon.cache.objectcache.ObjectCacheKey;
import org.sventon.util.EncodingUtils;
import org.sventon.util.ImageScaler;
import org.sventon.util.WebUtils;
import org.sventon.web.UserRepositoryContext;
import org.sventon.web.command.BaseCommand;
import javax.activation.FileTypeMap;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import static org.sventon.util.WebUtils.CONTENT_DISPOSITION_HEADER;
/**
* Controller used when downloading single files.
* <p/>
* Non-image files will be gotten as <i>attachment</i> using it's corresponding
* mime-type or <i>application/octet-stream</i> if unknown.
* <p/>
* Image files can be gotten in two different ways.
* <ul>
* <li><b>inline</b> - Gets the image with correct content type.
* Image will be displayed inline in browser.</li>
* <li><b>attachment</b> - Gets the image with content type
* application/octetstream. A download dialog will appear in browser.</li>
* </ul>
*
* @author jesper@sventon.org
*/
public class GetFileController extends AbstractTemplateController {
/**
* The mime/file type map.
*/
private FileTypeMap mimeFileTypeMap;
/**
* Object cache manager instance.
*/
private ObjectCacheManager objectCacheManager;
/**
* Image format name to use when generating thumbnails.
*/
private String imageFormatName;
/**
* Specifies the maximum horizontal/vertical size (in pixels) for a generated thumbnail image.
*/
private int maxThumbnailSize;
/**
* Image scaler.
*/
private ImageScaler imageScaler;
/**
* Display parameter.
*/
public static final String DISPLAY_REQUEST_PARAMETER = "disp";
/**
* Inline mode.
*/
public static final String CONTENT_DISPOSITION_INLINE = "inline";
/**
* Attachment mode.
*/
public static final String CONTENT_DISPOSITION_ATTACHMENT = "attachment";
/**
* Thumbnail mode.
*/
public static final String DISPLAY_TYPE_THUMBNAIL = "thumbnail";
@Override
protected ModelAndView svnHandle(final SVNConnection connection, final BaseCommand command,
final long headRevision, final UserRepositoryContext userRepositoryContext,
final HttpServletRequest request, final HttpServletResponse response,
final BindException exception) throws Exception {
final String displayType = ServletRequestUtils.getStringParameter(request, DISPLAY_REQUEST_PARAMETER, CONTENT_DISPOSITION_ATTACHMENT);
final OutputStream output = response.getOutputStream();
final boolean cacheUsed = getRepositoryConfiguration(command.getName()).isCacheUsed();
ObjectCache objectCache;
ObjectCacheKey cacheKey;
byte[] thumbnailData;
if (CONTENT_DISPOSITION_ATTACHMENT.equals(displayType)) {
logger.debug("Getting file as 'attachment'");
prepareResponse(CONTENT_DISPOSITION_ATTACHMENT, request, response, getMimeType(command.getTarget().toLowerCase()), command);
getRepositoryService().getFileContents(connection, command.getPath(), command.getRevisionNumber(), output);
} else if (CONTENT_DISPOSITION_INLINE.equals(displayType)) {
if (isImageFile(command.getPath())) {
logger.debug("Getting file as 'inline'");
prepareResponse(CONTENT_DISPOSITION_INLINE, request, response, getContentType(command.getPath()), command);
getRepositoryService().getFileContents(connection, command.getPath(), command.getRevisionNumber(), output);
} else {
logger.warn("File [" + command.getTarget() + "] is not an image file - unable to display it 'inline'");
prepareResponse(CONTENT_DISPOSITION_ATTACHMENT, request, response, getMimeType(command.getTarget().toLowerCase()), command);
getRepositoryService().getFileContents(connection, command.getPath(), command.getRevisionNumber(), output);
}
} else if (DISPLAY_TYPE_THUMBNAIL.equals(displayType)) {
if (isImageFile(command.getPath())) {
logger.debug("Getting file as 'thumbnail'");
prepareResponse(CONTENT_DISPOSITION_INLINE, request, response, getContentType(command.getPath()), command);
if (cacheUsed) {
objectCache = objectCacheManager.getCache(command.getName());
cacheKey = new ObjectCacheKey(command.getPath(), command.getRevisionNumber());
logger.debug("Using cachekey: " + cacheKey);
thumbnailData = (byte[]) objectCache.get(cacheKey);
if (thumbnailData == null) {
// Thumbnail did not exist - create it and cache it
thumbnailData = createThumbnail(connection, command);
logger.debug("Caching thumbnail. Using cachekey: " + cacheKey);
objectCache.put(cacheKey, thumbnailData);
objectCache.flush();
}
} else {
// Cache is not used - always recreate the thumbnail
thumbnailData = createThumbnail(connection, command);
}
output.write(thumbnailData);
} else {
logger.error("File '" + command.getTarget() + "' is not a image file");
}
} else {
throw new IllegalArgumentException("Illegal parameter '" + DISPLAY_REQUEST_PARAMETER + "':" + displayType);
}
output.flush();
output.close();
return null;
}
protected String getContentType(final String path) {
return mimeFileTypeMap.getContentType(path);
}
protected String getMimeType(final String path) {
String mimeType = WebUtils.APPLICATION_OCTET_STREAM;
try {
mimeType = getServletContext().getMimeType(path);
} catch (IllegalStateException ise) {
logger.debug("Could not get mimeType for file as an ApplicationContext does not exist. Using default");
}
return mimeType != null ? mimeType : WebUtils.APPLICATION_OCTET_STREAM;
}
protected boolean isImageFile(final String path) {
return mimeFileTypeMap.getContentType(path).startsWith("image");
}
/**
* Prepares the response by setting headers and content type.
*
* @param disposition Content disposition.
* @param request Request.
* @param response Response.
* @param contentType Content type.
* @param command Command.
*/
protected void prepareResponse(final String disposition, final HttpServletRequest request,
final HttpServletResponse response, final String contentType,
final BaseCommand command) {
response.setContentType(contentType);
response.setHeader(CONTENT_DISPOSITION_HEADER, disposition + "; filename=\"" +
EncodingUtils.encodeFilename(command.getTarget(), request.getHeader("USER-AGENT")) + "\"");
}
/**
* Creates a thumbnail version of a full size image.
*
* @param connection Repository
* @param command Command
* @return array of image bytes
*/
private byte[] createThumbnail(final SVNConnection connection, final BaseCommand command) {
logger.debug("Creating thumbnail for: " + command.getPath());
final ByteArrayOutputStream fullSizeImageData = new ByteArrayOutputStream();
final ByteArrayOutputStream thumbnailImageData = new ByteArrayOutputStream();
try {
getRepositoryService().getFileContents(connection, command.getPath(), command.getRevisionNumber(), fullSizeImageData);
final BufferedImage image = ImageIO.read(new ByteArrayInputStream(fullSizeImageData.toByteArray()));
ImageIO.write(imageScaler.getThumbnail(image, maxThumbnailSize), imageFormatName, thumbnailImageData);
} catch (final Exception ex) {
logger.warn("Unable to create thumbnail", ex);
}
return thumbnailImageData.toByteArray();
}
/**
* Sets the image scaler.
*
* @param imageScaler Image scaler
*/
public void setImageScaler(final ImageScaler imageScaler) {
this.imageScaler = imageScaler;
}
/**
* Sets the object cache manager instance.
*
* @param objectCacheManager The cache manager instance.
*/
public void setObjectCacheManager(final ObjectCacheManager objectCacheManager) {
this.objectCacheManager = objectCacheManager;
}
/**
* Sets the image format name.
*
* @param imageFormatName The format name, e.g. <tt>png</tt>.
*/
public void setImageFormatName(final String imageFormatName) {
this.imageFormatName = imageFormatName;
}
/**
* Sets the maximum vertical/horizontal size in pixels for the generated thumbnail images.
*
* @param maxSize Size in pixels.
*/
public void setMaxThumbnailSize(final int maxSize) {
this.maxThumbnailSize = maxSize;
}
/**
* Sets the mime/file type map.
*
* @param mimeFileTypeMap Map.
*/
public void setMimeFileTypeMap(final FileTypeMap mimeFileTypeMap) {
this.mimeFileTypeMap = mimeFileTypeMap;
}
}