/*
* ====================================================================
* 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.apache.commons.io.FilenameUtils;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.sventon.Colorer;
import org.sventon.SVNConnection;
import org.sventon.model.*;
import org.sventon.util.EncodingUtils;
import org.sventon.util.WebUtils;
import org.sventon.web.UserRepositoryContext;
import org.sventon.web.command.BaseCommand;
import javax.activation.FileTypeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* ShowFileController.
*
* @author patrik@sventon.org
* @author jesper@sventon.org
*/
public final class ShowFileController extends AbstractTemplateController {
/**
* The colorer instance.
*/
private Colorer colorer;
/**
* The mime/file type map.
*/
private final FileTypeMap mimeFileTypeMap;
/**
* Regex pattern that identifies text file extensions.
*/
private String textFileExtensionPattern;
/**
* Regex pattern that identifies binary file extensions.
*/
private String binaryFileExtensionPattern;
/**
* Regex pattern that identifies archive file extensions.
*/
private String archiveFileExtensionPattern;
/**
* FORMAT_REQUEST_PARAMETER = format.
*/
private static final String FORMAT_REQUEST_PARAMETER = "format";
/**
* Request parameter identifying the arcived entry to display.
*/
private static final String ARCHIVED_ENTRY = "archivedEntry";
/**
* Request parameter controlling if archived entry should be displayed
* independently of it's mime-type.
*/
private static final String FORCE_ARCHIVED_ENTRY_DISPLAY = "forceDisplay";
/**
* Request parameter indicating display should be done in a raw, unprocessed format.
*/
private static final String RAW_DISPLAY_FORMAT = "raw";
/**
* Constructor.
*
* @param mimeFileTypeMap Mime file type map.
*/
public ShowFileController(final FileTypeMap mimeFileTypeMap) {
this.mimeFileTypeMap = mimeFileTypeMap;
}
@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 {
logger.debug("Assembling file contents for: " + command);
final String formatParameter = ServletRequestUtils.getStringParameter(request, FORMAT_REQUEST_PARAMETER, null);
final String archivedEntry = ServletRequestUtils.getStringParameter(request, ARCHIVED_ENTRY, null);
final boolean forceDisplay = ServletRequestUtils.getBooleanParameter(request, FORCE_ARCHIVED_ENTRY_DISPLAY, false);
final Map<String, Object> model = new HashMap<String, Object>();
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final Properties fileProperties = getRepositoryService().listProperties(
connection, command.getPath(), command.getRevisionNumber());
final String charset = userRepositoryContext.getCharset();
logger.debug("Using charset encoding: " + charset);
model.put("properties", fileProperties);
final ModelAndView modelAndView;
if (isImageFileExtension(command)) {
logger.debug("File identified as an image file");
modelAndView = new ModelAndView("showImageFile", model);
} else if (isArchiveFileExtension(command)) {
if (archivedEntry == null) {
logger.debug("File identified as an archive file");
getRepositoryService().getFileContents(connection, command.getPath(), command.getRevisionNumber(), outStream);
final ArchiveFile archiveFile = new ArchiveFile(outStream.toByteArray());
model.put("entries", archiveFile.getEntries());
modelAndView = new ModelAndView("showArchiveFile", model);
} else {
logger.debug("Archived entry: " + archivedEntry);
model.put("archivedEntry", archivedEntry);
final String contentType = mimeFileTypeMap.getContentType(archivedEntry);
logger.debug("Detected content-type: " + contentType);
if (contentType != null && contentType.startsWith("text") || forceDisplay) {
getRepositoryService().getFileContents(connection, command.getPath(), command.getRevisionNumber(), outStream);
logger.debug("Extracting [" + archivedEntry + "] from archive [" + command.getPath() + "]");
final ZipFileWrapper zipFileWrapper = new ZipFileWrapper(outStream.toByteArray());
final TextFile textFile = new TextFile(new String(zipFileWrapper.extractFile(archivedEntry), charset),
archivedEntry, charset, colorer);
model.put("file", textFile);
modelAndView = new ModelAndView("showTextFile", model);
} else {
modelAndView = new ModelAndView("showBinaryFile", model);
}
}
} else if (isBinaryFileExtension(command)) {
logger.debug("File identified as a binary file");
modelAndView = new ModelAndView("showBinaryFile", model);
} else if (isTextFileExtension(command) || isTextMimeType(fileProperties)) {
getRepositoryService().getFileContents(connection, command.getPath(), command.getRevisionNumber(), outStream);
if (RAW_DISPLAY_FORMAT.equals(formatParameter)) {
final String content = outStream.toString(charset);
response.setHeader(WebUtils.CONTENT_DISPOSITION_HEADER, "inline; filename=\"" +
EncodingUtils.encodeFilename(command.getTarget(), request.getHeader("USER-AGENT")) + "\"");
response.setContentType(WebUtils.CONTENT_TYPE_TEXT_PLAIN);
response.getOutputStream().write(content.getBytes(charset));
return null;
} else {
final TextFile textFile = new TextFile(outStream.toString(charset), command.getPath(), charset, colorer);
model.put("file", textFile);
}
modelAndView = new ModelAndView("showTextFile", model);
} else {
logger.debug("File unidentified - showing as binary");
modelAndView = new ModelAndView("showBinaryFile", model);
}
return modelAndView;
}
/**
* Checks if given map of svn properties contains a text file mime type.
*
* @param properties The svn properties for given file.
* @return True if text file, false if not.
*/
protected boolean isTextMimeType(final Properties properties) {
final String mimeType = properties.getStringValue(Property.MIME_TYPE);
return (mimeType == null || mimeType.startsWith("text/"));
}
/**
* Checks if given file name indicates a text file.
*
* @param command Command
* @return True if text file, false if not.
*/
boolean isTextFileExtension(final BaseCommand command) {
return FilenameUtils.getExtension(command.getPath()).toLowerCase().matches(textFileExtensionPattern);
}
/**
* Checks if given file name indicates a binary file.
*
* @param command Command
* @return True if binary file, false if not.
*/
boolean isBinaryFileExtension(final BaseCommand command) {
return FilenameUtils.getExtension(command.getPath()).toLowerCase().matches(binaryFileExtensionPattern);
}
/**
* Checks if given file name indicates an archive file.
*
* @param command Command
* @return True if archive file, false if not.
*/
protected boolean isArchiveFileExtension(final BaseCommand command) {
return FilenameUtils.getExtension(command.getPath()).toLowerCase().matches(archiveFileExtensionPattern);
}
/**
* Checks if given file name indicates an image file.
*
* @param command Command
* @return True if image file, false if not.
*/
protected boolean isImageFileExtension(final BaseCommand command) {
return mimeFileTypeMap.getContentType(command.getPath()).startsWith("image");
}
/**
* Sets the <tt>Colorer</tt> instance.
*
* @param colorer The instance.
*/
public void setColorer(final Colorer colorer) {
this.colorer = colorer;
}
/**
* Sets the text file extension pattern.
*
* @param fileExtensionPattern The pattern
*/
public void setTextFileExtensionPattern(final String fileExtensionPattern) {
textFileExtensionPattern = fileExtensionPattern;
}
/**
* Sets the binary file extension pattern.
*
* @param fileExtensionPattern The pattern
*/
public void setBinaryFileExtensionPattern(final String fileExtensionPattern) {
binaryFileExtensionPattern = fileExtensionPattern;
}
/**
* Sets the archive file extension pattern.
*
* @param fileExtensionPattern The pattern
*/
public void setArchiveFileExtensionPattern(final String fileExtensionPattern) {
archiveFileExtensionPattern = fileExtensionPattern;
}
}