NIHVIVO-161 First cut at the controller.

This commit is contained in:
jeb228 2010-06-30 12:37:00 +00:00
parent 0a572e5620
commit 7ae2136362
2 changed files with 873 additions and 0 deletions

View file

@ -0,0 +1,516 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.ConfigurationProperties;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.filestorage.FileModelHelper;
import edu.cornell.mannlib.vitro.webapp.filestorage.FileServingHelper;
import edu.cornell.mannlib.vitro.webapp.filestorage.backend.FileStorage;
import edu.cornell.mannlib.vitro.webapp.filestorage.backend.FileStorageSetup;
import edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest;
import freemarker.template.Configuration;
/**
* Handle adding, replacing or deleting the main image on an Individual.
*/
public class ImageUploadController extends FreeMarkerHttpServlet {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory
.getLog(ImageUploadController.class);
private static final String DEFAULT_NAMESPACE = ConfigurationProperties
.getProperty("Vitro.defaultNamespace");
public static final String DUMMY_THUMBNAIL_URL = "/images/dummyImages/person.thumbnail.jpg";
/** Limit file size to 50 megabytes. */
public static final int MAXIMUM_FILE_SIZE = 50 * 1024 * 1024;
/** Generated thumbnails will be this big. */
public static final int THUMBNAIL_HEIGHT = 115;
public static final int THUMBNAIL_WIDTH = 115;
public static final String PARAMETER_ACTION = "action";
public static final String PARAMETER_ENTITY_URI = "entityUri";
public static final String PARAMETER_UPLOADED_FILE = "datafile";
public static final String ACTION_SAVE = "save";
public static final String ACTION_UPLOAD = "upload";
public static final String ACTION_DELETE = "delete";
public static final String BODY_TITLE = "title";
public static final String BODY_ENTITY_NAME = "entityName";
public static final String BODY_MAIN_IMAGE_URL = "imageUrl";
public static final String BODY_THUMBNAIL_URL = "thumbnailUrl";
public static final String BODY_CANCEL_URL = "cancelUrl";
public static final String BODY_DELETE_URL = "deleteUrl";
public static final String BODY_FORM_ACTION = "formAction";
public static final String BODY_ERROR_MESSAGE = "errorMessage";
public static final String TEMPLATE_NEW = "imageUpload/newImage.ftl";
public static final String TEMPLATE_REPLACE = "imageUpload/replaceImage.ftl";
public static final String TEMPLATE_CROP = "imageUpload/cropImage.ftl";
public static final String TEMPLATE_BOGUS = "imageUpload/bogus.ftl"; // TODO
// This
// is
// BOGUS!!
private static final String URL_HERE = UrlBuilder.getUrl("/uploadImages");
private FileStorage fileStorage;
/**
* When initialized, get a reference to the File Storage system. Without
* that, we can do nothing.
*/
@Override
public void init() throws ServletException {
super.init();
Object o = getServletContext().getAttribute(
FileStorageSetup.ATTRIBUTE_NAME);
if (o instanceof FileStorage) {
fileStorage = (FileStorage) o;
} else if (o == null) {
throw new UnavailableException(this.getClass().getSimpleName()
+ " could not initialize. Attribute '"
+ FileStorageSetup.ATTRIBUTE_NAME
+ "' was not set in the servlet context.");
} else {
throw new UnavailableException(this.getClass().getSimpleName()
+ " could not initialize. Attribute '"
+ FileStorageSetup.ATTRIBUTE_NAME
+ "' in the servlet context contained an instance of '"
+ o.getClass().getName() + "' instead of '"
+ FileStorage.class.getName() + "'");
}
}
/**
* <p>
* Parse the multi-part request before letting the
* {@link FreeMarkerHttpServlet} do its tricks.
* </p>
* <p>
* If the request was a multi-part file upload, it will parse to a
* normal-looking request with a "file_item_map" attribute.
* </p>
*/
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
try {
FileUploadServletRequest parsedRequest = FileUploadServletRequest
.parseRequest(request, MAXIMUM_FILE_SIZE);
if (log.isTraceEnabled()) {
dumpRequestDetails(parsedRequest);
}
super.doGet(parsedRequest, response);
} catch (FileUploadException e) {
// Swallow throw an exception here. Test for FILE_ITEM_MAP later.
log.error("Failed to parse the multi-part HTTP request", e);
}
}
protected String getTitle(String siteName) {
return "Photo Upload " + siteName;
}
/**
* Handle the different possible actions - default action is to show the
* intro screen.
*/
protected String getBody(VitroRequest vreq, Map<String, Object> body,
Configuration config) {
String action = vreq.getParameter(PARAMETER_ACTION);
try {
Individual entity = validateEntityUri(vreq);
if (ACTION_UPLOAD.equals(action)) {
return doUploadImage(vreq, body, config, entity);
} else if (ACTION_SAVE.equals(action)) {
return doCreateThumbnail(vreq, body, config, entity);
} else if (ACTION_DELETE.equals(action)) {
return doDeleteImage(body, config, entity);
} else {
return doIntroScreen(body, config, entity);
}
} catch (UserMistakeException e) {
return showAddImagePageWithError(body, config, null, e.getMessage());
} catch (Exception e) {
// We weren't expecting this - dump as much info as possible.
log.error(e, e);
return doError(e.toString(), body, config);
}
}
/**
* Show the first screen in the upload process: Add or Replace.
*/
private String doIntroScreen(Map<String, Object> body,
Configuration config, Individual entity) {
String thumbUrl = getThumbnailUrl(entity);
if (thumbUrl == null) {
return showAddImagePage(body, config, entity);
} else {
return showReplaceImagePage(body, config, entity, thumbUrl);
}
}
/**
* The user has selected their main image file. Remove any previous main
* image (and thumbnail), and attach the new main image.
*/
private String doUploadImage(VitroRequest vreq, Map<String, Object> body,
Configuration config, Individual entity) {
ImageUploadHelper helper = new ImageUploadHelper(fileStorage,
getWebappDaoFactory());
// Did they provide a file to upload? If not, show an error.
FileItem fileItem;
try {
fileItem = helper.validateImageFromRequest(vreq);
} catch (UserMistakeException e) {
String thumbUrl = getThumbnailUrl(entity);
String message = e.getMessage();
if (thumbUrl == null) {
return showAddImagePageWithError(body, config, entity, message);
} else {
return showReplaceImagePageWithError(body, config, entity,
thumbUrl, message);
}
}
// Remove the old main image (if any) and store the new one.
helper.removeExistingImage(entity);
helper.storeMainImageFile(entity, fileItem);
// The entity Individual is stale - get another one;
String entityUri = entity.getURI();
entity = getWebappDaoFactory().getIndividualDao().getIndividualByURI(
entityUri);
// Go to the cropping page.
return showCropImagePage(body, config, entity, getMainImageUrl(entity));
}
/**
* The user has specified how to crop the thumbnail. Crop it and attach it
* to the main image.
*/
private String doCreateThumbnail(VitroRequest vreq,
Map<String, Object> body, Configuration config, Individual entity) {
ImageUploadHelper helper = new ImageUploadHelper(fileStorage,
getWebappDaoFactory());
validateMainImage(entity);
CropRectangle crop = validateCropCoordinates(vreq);
helper.removeExistingThumbnail(entity);
helper.generateThumbnailAndStore(entity, crop);
return showIndividualDisplayPage(body, config, entity);
}
/**
* Delete the main image and the thumbnail from the individual.
*/
private String doDeleteImage(Map<String, Object> body,
Configuration config, Individual entity) {
ImageUploadHelper helper = new ImageUploadHelper(fileStorage,
getWebappDaoFactory());
helper.removeExistingImage(entity);
return showIndividualDisplayPage(body, config, entity);
}
/**
* Display a error message to the user.
*
* @message The text of the error message.
*/
private String doError(String message, Map<String, Object> body,
Configuration config) {
String bodyTemplate = "errorMessage.ftl";
body.put("errorMessage", message);
return mergeBodyToTemplate(bodyTemplate, body, config);
}
/**
* We need to be talking about an actual Individual here.
*/
private Individual validateEntityUri(VitroRequest vreq)
throws UserMistakeException {
String entityUri = vreq.getParameter(PARAMETER_ENTITY_URI);
if (entityUri == null) {
throw new UserMistakeException("No entity URI was provided");
}
Individual entity = getWebappDaoFactory().getIndividualDao()
.getIndividualByURI(entityUri);
if (entity == null) {
throw new UserMistakeException(
"This URI is not recognized as belonging to anyone: '"
+ entityUri + "'");
}
return entity;
}
/**
* We can't do a thumbnail if there is no main image.
*/
private void validateMainImage(Individual entity) {
if (entity.getMainImageUri() == null) {
throw new IllegalStateException("Can't store a thumbnail "
+ "on an individual with no main image: '"
+ showEntity(entity) + "'");
}
}
/**
* Did we get the cropping coordinates?
*/
private CropRectangle validateCropCoordinates(VitroRequest vreq) {
int x = getRequiredIntegerParameter(vreq, "x");
int y = getRequiredIntegerParameter(vreq, "y");
int h = getRequiredIntegerParameter(vreq, "h");
int w = getRequiredIntegerParameter(vreq, "w");
return new CropRectangle(x, y, h, w);
}
/**
* We need this parameter on the request, and it must be a valid integer.
*/
private int getRequiredIntegerParameter(HttpServletRequest req, String key) {
String string = req.getParameter(key);
if (string == null) {
throw new IllegalStateException(
"Request did not contain a value for '" + key + "'");
}
try {
return Integer.parseInt(string);
} catch (NumberFormatException e) {
throw new IllegalStateException("Value for '" + key
+ "' was not a valid integer: '" + string + "'");
}
}
/**
* Get the URL that will serve this entity's main image, or null.
*/
private String getMainImageUrl(Individual entity) {
String imageUri = FileModelHelper.getMainImageBytestreamUri(entity);
String imageFilename = FileModelHelper.getMainImageFilename(entity);
return FileServingHelper.getBytestreamAliasUrl(imageUri, imageFilename);
}
/**
* Get the URL that will serve this entity's thumbnail image, or null.
*/
private String getThumbnailUrl(Individual entity) {
String thumbUri = FileModelHelper.getThumbnailBytestreamUri(entity);
String thumbFilename = FileModelHelper.getThumbnailFilename(entity);
return FileServingHelper.getBytestreamAliasUrl(thumbUri, thumbFilename);
}
/**
* The individual has no image - go to the Add Image page.
*
* @param entity
* if this is null, then all URLs lead to the welcome page.
*/
private String showAddImagePage(Map<String, Object> body,
Configuration config, Individual entity) {
String formAction = (entity == null) ? "/" : formAction(
entity.getURI(), ACTION_UPLOAD);
String cancelUrl = (entity == null) ? "/" : displayPageUrl(entity
.getURI());
body.put(BODY_THUMBNAIL_URL, UrlBuilder.getUrl(DUMMY_THUMBNAIL_URL));
body.put(BODY_FORM_ACTION, formAction);
body.put(BODY_CANCEL_URL, cancelUrl);
body.put(BODY_TITLE, "Upload image" + forName(entity));
return mergeBodyToTemplate(TEMPLATE_NEW, body, config);
}
/**
* The individual has no image, but the user did something wrong.
*/
private String showAddImagePageWithError(Map<String, Object> body,
Configuration config, Individual entity, String message) {
body.put(BODY_ERROR_MESSAGE, message);
return showAddImagePage(body, config, entity);
}
/**
* The individual has an image - go to the Replace Image page.
*/
private String showReplaceImagePage(Map<String, Object> body,
Configuration config, Individual entity, String thumbUrl) {
body.put(BODY_THUMBNAIL_URL, UrlBuilder.getUrl(thumbUrl));
body.put(BODY_DELETE_URL, formAction(entity.getURI(), ACTION_DELETE));
body.put(BODY_FORM_ACTION, formAction(entity.getURI(), ACTION_UPLOAD));
body.put(BODY_CANCEL_URL, displayPageUrl(entity.getURI()));
body.put(BODY_TITLE, "Replace image" + forName(entity));
return mergeBodyToTemplate(TEMPLATE_REPLACE, body, config);
}
/**
* The individual has an image, but the user did something wrong.
*/
private String showReplaceImagePageWithError(Map<String, Object> body,
Configuration config, Individual entity, String thumbUrl,
String message) {
body.put(BODY_ERROR_MESSAGE, message);
return showReplaceImagePage(body, config, entity, thumbUrl);
}
/**
* We got their main image - go to the Crop Image page.
*/
private String showCropImagePage(Map<String, Object> body,
Configuration config, Individual entity, String imageUrl) {
body.put(BODY_MAIN_IMAGE_URL, UrlBuilder.getUrl(imageUrl));
body.put(BODY_FORM_ACTION, formAction(entity.getURI(), ACTION_SAVE));
body.put(BODY_CANCEL_URL, displayPageUrl(entity.getURI()));
body.put(BODY_TITLE, "Crop Photo" + forName(entity));
return mergeBodyToTemplate(TEMPLATE_CROP, body, config);
}
/**
* All done - go to the individual display page.
*/
private String showIndividualDisplayPage(Map<String, Object> body,
Configuration config, Individual entity) {
return mergeBodyToTemplate(TEMPLATE_BOGUS, body, config);
}
/**
* When we complete the process, by success or by cancellation, we go to the
* individual display page.
*/
private String displayPageUrl(String entityUri) {
if (DEFAULT_NAMESPACE == null) {
return UrlBuilder.getUrl("");
} else if (!entityUri.startsWith(DEFAULT_NAMESPACE)) {
return UrlBuilder.getUrl("");
} else {
String tail = entityUri.substring(DEFAULT_NAMESPACE.length());
if (!tail.startsWith("/")) {
tail = "/" + tail;
}
return UrlBuilder.getUrl("display" + tail);
}
}
/**
* The "action" parameter on the HTML "form" tag should include the path
* back to this controller, along with the desired action and the Entity
* URI.
*/
private String formAction(String entityUri, String action) {
UrlBuilder.Params params = new UrlBuilder.Params(PARAMETER_ENTITY_URI,
entityUri, PARAMETER_ACTION, action);
return UrlBuilder.getPath(URL_HERE, params);
}
/**
* Format an entity for display in a message.
*/
private String showEntity(Individual entity) {
if (entity == null) {
return String.valueOf(null);
} else if (entity.getName() == null) {
return "'no name' (" + entity.getURI() + ")";
} else {
return "'" + entity.getName() + "' (" + entity.getURI() + ")";
}
}
/**
* Format the entity's name for display as part of the page title.
*/
private String forName(Individual entity) {
if (entity != null) {
String name = entity.getName();
if (name != null) {
return " for " + name;
}
}
return "";
}
/**
* Holds an error message to use as a complaint to the user.
*/
static class UserMistakeException extends Exception {
UserMistakeException(String message) {
super(message);
}
}
/**
* Holds the coordinates that we use to crop the main image.
*/
static class CropRectangle {
final int x;
final int y;
final int height;
final int width;
private CropRectangle(int x, int y, int height, int width) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
}
}
/**
* For debugging, dump all sorts of information about the request.
*
* WARNING: if this request represents a Multi-part request which has not
* yet been parsed, just reading these parameters will consume them.
*/
@SuppressWarnings("unchecked")
private void dumpRequestDetails(HttpServletRequest req) {
log.trace("Request is " + req.getClass().getName());
Map<String, String[]> parms = req.getParameterMap();
for (Entry<String, String[]> entry : parms.entrySet()) {
log.trace("Parameter '" + entry.getKey() + "'="
+ Arrays.deepToString(entry.getValue()));
}
Enumeration<String> attrs = req.getAttributeNames();
while (attrs.hasMoreElements()) {
String key = attrs.nextElement();
String valueString = String.valueOf(req.getAttribute(key));
String valueOneLine = valueString.replace("\n", " | ");
log.trace("Attribute '" + key + "'=" + valueOneLine);
}
}
}

View file

@ -0,0 +1,357 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.PARAMETER_UPLOADED_FILE;
import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.THUMBNAIL_HEIGHT;
import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.THUMBNAIL_WIDTH;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.UserMistakeException;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.filestorage.FileModelHelper;
import edu.cornell.mannlib.vitro.webapp.filestorage.backend.FileAlreadyExistsException;
import edu.cornell.mannlib.vitro.webapp.filestorage.backend.FileStorage;
import edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest;
/**
* Handle the mechanics of validating, storing, and deleting file images.
*/
public class ImageUploadHelper {
private static final Log log = LogFactory.getLog(ImageUploadHelper.class);
/** Recognized file extensions mapped to MIME-types. */
private static final Map<String, String> RECOGNIZED_FILE_TYPES = createFileTypesMap();
private static Map<String, String> createFileTypesMap() {
Map<String, String> map = new HashMap<String, String>();
map.put(".gif", "image/gif");
map.put(".png", "image/png");
map.put(".jpg", "image/jpeg");
map.put(".jpeg", "image/jpeg");
map.put(".jpe", "image/jpeg");
return Collections.unmodifiableMap(map);
}
private final FileModelHelper fileModelHelper;
private final FileStorage fileStorage;
ImageUploadHelper(FileStorage fileStorage, WebappDaoFactory webAppDaoFactory) {
this.fileModelHelper = new FileModelHelper(webAppDaoFactory);
this.fileStorage = fileStorage;
}
/**
* The image must be present and non-empty, and must have a mime-type that
* represents an image we support.
*
* We rely on the fact that a {@link FileUploadServletRequest} will always
* have a map of {@link FileItem}s, even if it is empty. However, that map
* may not contain the field that we want, or that field may contain an
* empty file.
*
* @throws UserMistakeException
* if there is no file, if it is empty, or if it is not an image
* file.
*/
@SuppressWarnings("unchecked")
FileItem validateImageFromRequest(HttpServletRequest request)
throws UserMistakeException {
Map<String, List<FileItem>> map = (Map<String, List<FileItem>>) request
.getAttribute(FileUploadServletRequest.FILE_ITEM_MAP);
if (map == null) {
throw new IllegalStateException("Failed to parse the "
+ "multi-part request for uploading an image.");
}
List<FileItem> list = map.get(PARAMETER_UPLOADED_FILE);
if ((list == null) || list.isEmpty()) {
throw new UserMistakeException("The form did not contain a '"
+ PARAMETER_UPLOADED_FILE + "' field.");
}
FileItem file = list.get(0);
if (file.getSize() == 0) {
throw new UserMistakeException("No file was uploaded in '"
+ PARAMETER_UPLOADED_FILE + "'");
}
String filename = getSimpleFilename(file);
String mimeType = getMimeType(file);
if (!RECOGNIZED_FILE_TYPES.containsValue(mimeType)) {
throw new UserMistakeException("'" + filename
+ "' is not a recognized image file type. "
+ "These are the recognized types: "
+ RECOGNIZED_FILE_TYPES);
}
return file;
}
/**
* If this entity already had a main image, remove the connection. If the
* image and the thumbnail are no longer used by anyone, remove them from
* the model, and from the file system.
*/
void removeExistingImage(Individual person) {
Individual mainImage = fileModelHelper.removeMainImage(person);
if (mainImage == null) {
return;
}
removeExistingThumbnail(person);
if (!fileModelHelper.isFileReferenced(mainImage)) {
Individual bytes = FileModelHelper.getBytestreamForFile(mainImage);
if (bytes != null) {
try {
fileStorage.deleteFile(bytes.getURI());
} catch (IOException e) {
throw new IllegalStateException(
"Can't delete the main image file: '"
+ bytes.getURI() + "' for '"
+ person.getName() + "' ("
+ person.getURI() + ")", e);
}
}
fileModelHelper.removeFileFromModel(mainImage);
}
}
/**
* Store this image in the model and in the file storage system, and set it
* as the main image for this person.
*/
void storeMainImageFile(Individual person, FileItem imageFileItem) {
InputStream inputStream = null;
try {
inputStream = imageFileItem.getInputStream();
String mimeType = getMimeType(imageFileItem);
String filename = getSimpleFilename(imageFileItem);
// Create the file individuals in the model
Individual byteStream = fileModelHelper
.createByteStreamIndividual();
Individual file = fileModelHelper.createFileIndividual(mimeType,
filename, byteStream);
// Store the file in the FileStorage system.
fileStorage.createFile(byteStream.getURI(), filename, inputStream);
// Set the file as the main image for the person.
fileModelHelper.setAsMainImageOnEntity(person, file);
} catch (FileAlreadyExistsException e) {
throw new IllegalStateException(
"Can't create the main image file for '" + person.getName()
+ "' (" + person.getURI() + ")" + e.getMessage(), e);
} catch (IOException e) {
throw new IllegalStateException(
"Can't create the main image file for '" + person.getName()
+ "' (" + person.getURI() + ")", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* If the entity already has a thumbnail, remove it. If there are no other
* references to the thumbnail, delete it from the model and from the file
* system.
*/
void removeExistingThumbnail(Individual person) {
Individual mainImage = FileModelHelper.getMainImage(person);
Individual thumbnail = FileModelHelper.getThumbnailForImage(mainImage);
if (thumbnail == null) {
return;
}
fileModelHelper.removeThumbnail(person);
if (!fileModelHelper.isFileReferenced(thumbnail)) {
Individual bytes = FileModelHelper.getBytestreamForFile(thumbnail);
if (bytes != null) {
try {
fileStorage.deleteFile(bytes.getURI());
} catch (IOException e) {
throw new IllegalStateException(
"Can't delete the thumbnail file: '"
+ bytes.getURI() + "' for '"
+ person.getName() + "' ("
+ person.getURI() + ")", e);
}
}
fileModelHelper.removeFileFromModel(thumbnail);
}
}
/**
* Generate a thumbnail from the main image from it, store it in the model
* and in the file storage system, and set it as the thumbnail on the main
* image.
*/
void generateThumbnailAndStore(Individual person,
ImageUploadController.CropRectangle crop) {
String mainBytestreamUri = FileModelHelper
.getMainImageBytestreamUri(person);
String mainFilename = FileModelHelper.getMainImageFilename(person);
if (mainBytestreamUri == null) {
log.warn("Tried to generate a thumbnail on '" + person.getURI()
+ "', but there was no main image.");
return;
}
InputStream mainInputStream = null;
InputStream thumbInputStream = null;
try {
mainInputStream = fileStorage.getInputStream(mainBytestreamUri,
mainFilename);
thumbInputStream = scaleImageForThumbnail(mainInputStream, crop);
String mimeType = RECOGNIZED_FILE_TYPES.get(".jpg");
String filename = createThumbnailFilename(mainFilename);
// Create the file individuals in the model
Individual byteStream = fileModelHelper
.createByteStreamIndividual();
Individual file = fileModelHelper.createFileIndividual(mimeType,
filename, byteStream);
// Store the file in the FileStorage system.
fileStorage.createFile(byteStream.getURI(), filename,
thumbInputStream);
// Set the file as the thumbnail on the main image for the person.
fileModelHelper.setThumbnailOnIndividual(person, file);
} catch (FileAlreadyExistsException e) {
throw new IllegalStateException("Can't create the thumbnail file: "
+ e.getMessage(), e);
} catch (IOException e) {
throw new IllegalStateException("Can't create the thumbnail file",
e);
} finally {
if (mainInputStream != null) {
try {
mainInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (thumbInputStream != null) {
try {
thumbInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Internet Explorer and Opera will give you the full path along with the
* filename. This will remove the path.
*/
private String getSimpleFilename(FileItem item) {
String fileName = item.getName();
if (fileName == null) {
return null;
} else {
return FilenameUtils.getName(fileName);
}
}
/**
* Get the MIME type as supplied by the browser. If none, try to infer it
* from the filename extension and the map of recognized MIME types.
*/
private String getMimeType(FileItem file) {
String mimeType = file.getContentType();
if (mimeType != null) {
return mimeType;
}
String filename = getSimpleFilename(file);
int periodHere = filename.lastIndexOf('.');
if (periodHere == -1) {
return null;
}
String extension = filename.substring(periodHere);
return RECOGNIZED_FILE_TYPES.get(extension);
}
/**
* Create a name for the thumbnail from the name of the original file.
* "myPicture.anything" becomes "thumbnail_myPicture.jpg".
*/
private String createThumbnailFilename(String filename) {
String prefix = "thumbnail_";
String extension = ".jpg";
int periodHere = filename.lastIndexOf('.');
if (periodHere == -1) {
return prefix + filename + extension;
} else {
return prefix + filename.substring(0, periodHere) + extension;
}
}
/**
* Create a thumbnail from a source image, given a cropping rectangle (x, y,
* width, height).
*/
private InputStream scaleImageForThumbnail(InputStream source,
ImageUploadController.CropRectangle crop) throws IOException {
BufferedImage bsrc = ImageIO.read(source);
// Insure that x and y fall within the image dimensions.
int x = Math.max(0, Math.min(bsrc.getWidth(), crop.x));
int y = Math.max(0, Math.min(bsrc.getHeight(), crop.y));
// Insure that width and height are reasonable.
int w = Math.max(5, Math.min(bsrc.getWidth() - x, crop.width));
int h = Math.max(5, Math.min(bsrc.getHeight() - y, crop.height));
// Figure the scales.
double scaleWidth = ((double) THUMBNAIL_WIDTH) / ((double) w);
double scaleHeight = ((double) THUMBNAIL_HEIGHT) / ((double) h);
// Create the transform.
AffineTransform at = new AffineTransform();
at.translate(-x, -y);
at.scale(scaleWidth, scaleHeight);
// Apply the transform.
BufferedImage bdest = new BufferedImage(crop.width, crop.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = bdest.createGraphics();
g.drawRenderedImage(bsrc, at);
// Get an input stream.
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ImageIO.write(bdest, "JPG", buffer);
return new ByteArrayInputStream(buffer.toByteArray());
}
}