diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadController.java index 5efe25e2b..87d6947b5 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadController.java @@ -29,6 +29,7 @@ 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.model.FileInfo; import edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest; import freemarker.template.Configuration; @@ -281,37 +282,22 @@ public class ImageUploadController extends FreeMarkerHttpServlet { ImageUploadHelper helper = new ImageUploadHelper(fileStorage, vreq .getFullWebappDaoFactory()); - // Did they provide a file to upload? If not, show an error. - FileItem fileItem; try { - fileItem = helper.validateImageFromRequest(vreq); + // Did they provide a file to upload? If not, show an error. + FileItem fileItem = helper.validateImageFromRequest(vreq); + // Put it in the file system, and store a reference in the session. + FileInfo fileInfo = helper.storeNewImage(fileItem, vreq); + + // How big is the new image? If not big enough, show an error. + Dimensions size = helper.getNewImageSize(fileInfo); + + // Go to the cropping page. + return showCropImagePage(vreq, entity, fileInfo + .getBytestreamAliasUrl(), size); } catch (UserMistakeException e) { return showErrorMessage(vreq, entity, e.getMessage()); } - - // 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 = vreq.getFullWebappDaoFactory().getIndividualDao() - .getIndividualByURI(entityUri); - - Dimensions mainImageSize = helper.getMainImageSize(entity); - - if ((mainImageSize.height < THUMBNAIL_HEIGHT) - || (mainImageSize.width < THUMBNAIL_WIDTH)) { - String message = "The uploaded image should be at least " - + THUMBNAIL_HEIGHT + " pixels high and " + THUMBNAIL_WIDTH - + " pixels wide."; - return showErrorMessage(vreq, entity, message); - } - - // Go to the cropping page. - return showCropImagePage(vreq, entity, getMainImageUrl(entity), - mainImageSize); } /** @@ -338,13 +324,18 @@ public class ImageUploadController extends FreeMarkerHttpServlet { ImageUploadHelper helper = new ImageUploadHelper(fileStorage, vreq .getFullWebappDaoFactory()); - validateMainImage(entity); - CropRectangle crop = validateCropCoordinates(vreq); + try { + CropRectangle crop = validateCropCoordinates(vreq); + FileInfo newImage = helper.getNewImageInfo(vreq); + FileInfo thumbnail = helper.generateThumbnail(crop, newImage); - helper.removeExistingThumbnail(entity); - helper.generateThumbnailAndStore(entity, crop); + helper.removeExistingImage(entity); + helper.storeImageFiles(entity, newImage, thumbnail); - return showExitPage(vreq, entity); + return showExitPage(vreq, entity); + } catch (UserMistakeException e) { + return showErrorMessage(vreq, entity, e.getMessage()); + } } /** @@ -379,17 +370,6 @@ public class ImageUploadController extends FreeMarkerHttpServlet { 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? */ @@ -552,19 +532,6 @@ public class ImageUploadController extends FreeMarkerHttpServlet { 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. */ diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadHelper.java index 6415d43cc..465ebdc32 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ImageUploadHelper.java @@ -31,13 +31,16 @@ 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.VitroRequest; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.CropRectangle; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.ImageUploadController.Dimensions; 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.TempFileHolder; 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.model.FileInfo; import edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest; /** @@ -46,6 +49,12 @@ import edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServ public class ImageUploadHelper { private static final Log log = LogFactory.getLog(ImageUploadHelper.class); + /** + * When they upload a new image, store it as this session attribute until + * we're ready to attach it to the Individual. + */ + public static final String ATTRIBUTE_TEMP_FILE = "ImageUploadHelper.tempFile"; + /** * If the main image is larger than this, it will be displayed at reduced * scale. @@ -65,10 +74,12 @@ public class ImageUploadHelper { return Collections.unmodifiableMap(map); } + private final WebappDaoFactory webAppDaoFactory; private final FileModelHelper fileModelHelper; private final FileStorage fileStorage; ImageUploadHelper(FileStorage fileStorage, WebappDaoFactory webAppDaoFactory) { + this.webAppDaoFactory = webAppDaoFactory; this.fileModelHelper = new FileModelHelper(webAppDaoFactory); this.fileStorage = fileStorage; } @@ -118,12 +129,158 @@ public class ImageUploadHelper { String mimeType = getMimeType(file); if (!RECOGNIZED_FILE_TYPES.containsValue(mimeType)) { throw new UserMistakeException("'" + filename - + "' is not a recognized image file type. Please upload JPEG, GIF, or PNG files only."); + + "' is not a recognized image file type. " + + "Please upload JPEG, GIF, or PNG files only."); } return file; } + /** + * The user has uploaded a new main image, but we're not ready to assign it + * to them. + * + * Put it into the file storage system, and attach it as a temp file on the + * session until we need it. + */ + FileInfo storeNewImage(FileItem fileItem, VitroRequest vreq) { + InputStream inputStream = null; + try { + inputStream = fileItem.getInputStream(); + String mimeType = getMimeType(fileItem); + String filename = getSimpleFilename(fileItem); + WebappDaoFactory wadf = vreq.getWebappDaoFactory(); + + FileInfo fileInfo = FileModelHelper.createFile(fileStorage, wadf, + filename, mimeType, inputStream); + + TempFileHolder.attach(vreq.getSession(), ATTRIBUTE_TEMP_FILE, + fileInfo); + + return fileInfo; + } catch (FileAlreadyExistsException e) { + throw new IllegalStateException("Can't create the new image file.", + e); + } catch (IOException e) { + throw new IllegalStateException("Can't create the new image file.", + e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * Find out how big this image is. + * + * @throws UserMistakeException + * if the image is smaller than a thumbnail. + */ + Dimensions getNewImageSize(FileInfo fileInfo) throws UserMistakeException { + String uri = fileInfo.getBytestreamUri(); + String filename = fileInfo.getFilename(); + + InputStream stream = null; + try { + stream = fileStorage.getInputStream(uri, filename); + BufferedImage i = ImageIO.read(stream); + Dimensions size = new Dimensions(i.getWidth(), i.getHeight()); + log.debug("new image size is " + size); + + if ((size.height < THUMBNAIL_HEIGHT) + || (size.width < THUMBNAIL_WIDTH)) { + throw new UserMistakeException( + "The uploaded image should be at least " + + THUMBNAIL_HEIGHT + " pixels high and " + + THUMBNAIL_WIDTH + " pixels wide."); + } + + return size; + } catch (FileNotFoundException e) { + throw new IllegalStateException("File not found: " + fileInfo, e); + } catch (IOException e) { + throw new IllegalStateException("Can't read image file: " + + fileInfo, e); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * Get the info for the new image, from where we stored it in the session. + * + * @throws UserMistakeException + * if it isn't there. + */ + FileInfo getNewImageInfo(VitroRequest vreq) throws UserMistakeException { + FileInfo fileInfo = TempFileHolder.remove(vreq.getSession(), + ATTRIBUTE_TEMP_FILE); + + if (fileInfo == null) { + throw new UserMistakeException( + "There is no image file to be cropped."); + } + + return fileInfo; + } + + /** + * Crop the main image to create the thumbnail, and put it into the file + * storage system. + */ + FileInfo generateThumbnail(CropRectangle crop, FileInfo newImage) { + InputStream mainStream = null; + InputStream thumbStream = null; + try { + String mainBytestreamUri = newImage.getBytestreamUri(); + String mainFilename = newImage.getFilename(); + mainStream = fileStorage.getInputStream(mainBytestreamUri, + mainFilename); + + thumbStream = scaleImageForThumbnail(mainStream, crop); + + String mimeType = RECOGNIZED_FILE_TYPES.get(".jpg"); + String filename = createThumbnailFilename(mainFilename); + + FileInfo fileInfo = FileModelHelper.createFile(fileStorage, + webAppDaoFactory, filename, mimeType, thumbStream); + log.debug("Created thumbnail: " + fileInfo); + return fileInfo; + } 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 (mainStream != null) { + try { + mainStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (thumbStream != null) { + try { + thumbStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + /** * 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 @@ -154,47 +311,6 @@ public class ImageUploadHelper { } } - /** - * 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 @@ -227,64 +343,12 @@ public class ImageUploadHelper { } /** - * 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. + * Store the image on the entity, and the thumbnail on the 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(); - } - } - } + void storeImageFiles(Individual entity, FileInfo newImage, + FileInfo thumbnail) { + FileModelHelper.setImagesOnEntity(webAppDaoFactory, entity, newImage, + thumbnail); } /** @@ -398,42 +462,4 @@ public class ImageUploadHelper { return crop.unscale(displayScale); } - /** - * Find out how big the main image is. - */ - Dimensions getMainImageSize(Individual entity) { - String uri = FileModelHelper.getMainImageBytestreamUri(entity); - String filename = FileModelHelper.getMainImageFilename(entity); - InputStream stream = null; - try { - stream = fileStorage.getInputStream(uri, filename); - BufferedImage i = ImageIO.read(stream); - Dimensions size = new Dimensions(i.getWidth(), i.getHeight()); - log.debug("main image size is " + size); - return size; - } catch (FileNotFoundException e) { - log.error( - "No main image file for '" + showUri(entity) + "'; name='" - + filename + "', bytestreamUri='" + uri + "'", e); - return new Dimensions(0, 0); - } catch (IOException e) { - log.error( - "Can't read main image file for '" + showUri(entity) - + "'; name='" + filename + "', bytestreamUri='" - + uri + "'", e); - return new Dimensions(0, 0); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - private String showUri(Individual entity) { - return (entity == null) ? "null" : entity.getURI(); - } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/FileModelHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/FileModelHelper.java index 54a32bd8f..e0e31ea03 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/FileModelHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/FileModelHelper.java @@ -2,6 +2,8 @@ package edu.cornell.mannlib.vitro.webapp.filestorage; +import java.io.IOException; +import java.io.InputStream; import java.util.List; import org.apache.commons.logging.Log; @@ -19,6 +21,9 @@ import edu.cornell.mannlib.vitro.webapp.dao.InsertException; import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyStatementDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +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.model.FileInfo; /** *
@@ -30,10 +35,66 @@ import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; * a parameter holds all necessary references for the operation. Other methods * require an instance, which is initialized with a {@link WebappDaoFactory}. *
+ *+ * TODO: This should be based around FileInfo and ImageInfo, as much as + * possible. + *
*/ public class FileModelHelper { private static final Log log = LogFactory.getLog(FileModelHelper.class); + // ---------------------------------------------------------------------- + // Methods based around FileInfo + // ---------------------------------------------------------------------- + + public static FileInfo createFile(FileStorage fileStorage, + WebappDaoFactory wadf, String filename, String mimeType, + InputStream inputStream) throws FileAlreadyExistsException, + IOException { + FileModelHelper fmh = new FileModelHelper(wadf); + + // Create the file individuals in the model + Individual byteStream = fmh.createByteStreamIndividual(); + String bytestreamUri = byteStream.getURI(); + Individual file = fmh.createFileIndividual(mimeType, filename, + byteStream); + String fileUri = file.getURI(); + + // Store the file in the FileStorage system. + fileStorage.createFile(bytestreamUri, filename, inputStream); + + // Figure out the alias URL + String aliasUrl = FileServingHelper.getBytestreamAliasUrl( + bytestreamUri, filename); + + // And wrap it all up in a tidy little package. + return new FileInfo.Builder().setFilename(filename).setMimeType( + mimeType).setUri(fileUri).setBytestreamUri(bytestreamUri) + .setBytestreamAliasUrl(aliasUrl).build(); + } + + /** + * Record this image file and thumbnail on this entity. NOTE: after this + * update, the entity object is stale. + */ + public static void setImagesOnEntity(WebappDaoFactory wadf, + Individual entity, FileInfo mainInfo, FileInfo thumbInfo) { + IndividualDao individualDao = wadf.getIndividualDao(); + + // Add the thumbnail file to the main image file. + ObjectPropertyStatementDao opsd = wadf.getObjectPropertyStatementDao(); + opsd.insertNewObjectPropertyStatement(new ObjectPropertyStatementImpl( + mainInfo.getUri(), VitroVocabulary.FS_THUMBNAIL_IMAGE, + thumbInfo.getUri())); + + // Add the main image file to the entity. + entity.setMainImageUri(mainInfo.getUri()); + individualDao.updateIndividual(entity); + + log.debug("Set images on '" + entity.getURI() + "': main=" + mainInfo + + ", thumb=" + thumbInfo); + } + // ---------------------------------------------------------------------- // Static methods -- the Individual holds all necessary references. // ---------------------------------------------------------------------- diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/TempFileHolder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/TempFileHolder.java new file mode 100644 index 000000000..7926dc7df --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/TempFileHolder.java @@ -0,0 +1,152 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.filestorage; + +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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.model.FileInfo; + +/** + * Attaches an uploaded file to the session with a listener, so the file will be + * deleted if + *