/******************************************************************************* * Copyright (c) 2012, Directors of the Tyndale STEP Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * Neither the name of the Tyndale House, Cambridge (www.TyndaleHouse.com) * nor the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ package com.tyndalehouse.step.rest.controllers; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.tyndalehouse.step.core.exceptions.StepInternalException; import com.tyndalehouse.step.core.utils.IOUtils; /** * Serves the images by downloading them from a remote source if they do not already exist. * * @author chrisburrell * */ @Singleton public class ImageController extends HttpServlet { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1721159652548642069L; /** The Constant LOGGER. */ private static final Logger LOGGER = LoggerFactory.getLogger(ImageController.class); /** The local source. */ private final String localSource; /** The remote source. */ private final String remoteSource; /** * Instantiates a new image controller. * * @param localSource a local place where files might be found * @param remoteSource a remote place where files might be found */ @Inject public ImageController(@Named("app.images.localSource") final String localSource, @Named("app.images.remoteSource") final String remoteSource) { this.localSource = localSource; this.remoteSource = remoteSource; } @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse response) throws ServletException, IOException { try { final String pathToImage = req.getRequestURI().substring( req.getContextPath().length() + req.getServletPath().length()); final File image = new File(this.localSource, pathToImage); if (!image.exists()) { // fetch from web and write out download(image, pathToImage, response); return; } LOGGER.trace("Returning image locally stored at: ", image.getAbsolutePath()); writeImage(image, response); } catch (final StepInternalException ex) { LOGGER.warn("An exception has occurred - image cannot be sent back", ex); } } /** * Downloads the image locally from the server. * * @param image the image file * @param pathToImage the path to the image * @param response the response for the user */ private void download(final File image, final String pathToImage, final HttpServletResponse response) { final HttpGet get = new HttpGet(this.remoteSource + pathToImage); final DefaultHttpClient client = new DefaultHttpClient(); // two streams for downloading OutputStream fileOutput = null; InputStream inputStream = null; try { final HttpResponse remoteResponse = client.execute(get); if (remoteResponse.getStatusLine().getStatusCode() != 200) { response.setStatus(remoteResponse.getStatusLine().getStatusCode()); throw new StepInternalException("Unable to obtain image remotely"); } final HttpEntity entity = remoteResponse.getEntity(); inputStream = entity.getContent(); long contentLength = entity.getContentLength(); if (contentLength < 0) { contentLength = Integer.MAX_VALUE; } // read the input from the external source final int imageDataLength = (int) contentLength; final byte[] imageData = new byte[imageDataLength]; inputStream.read(imageData, 0, imageDataLength); // write to the file fileOutput = new FileOutputStream(image); fileOutput.write(imageData, 0, imageDataLength); // while we have the data, let's write it to the response prepareImageResponse(imageDataLength, image.getName(), response); response.getOutputStream().write(imageData, 0, imageDataLength); } catch (final IOException e) { throw new StepInternalException("Unable to obtain image remotely", e); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(fileOutput); } } /** * writes the image to the response stream. * * @param image the image * @param response the response stream */ private void writeImage(final File image, final HttpServletResponse response) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(image); writeResponse(image, response, fileInputStream); } catch (final IOException e) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); throw new StepInternalException("Failed to write image to output response stream", e); } finally { IOUtils.closeQuietly(fileInputStream); } } /** * Writes the content type, length and data to the response stream. * * @param image the source image * @param response the response * @param bufferedInputStream the input stream * @throws IOException an exception while reading the file */ private void writeResponse(final File image, final HttpServletResponse response, final InputStream bufferedInputStream) throws IOException { // set the content size and mime type prepareImageResponse(getValidImageSize(image, response), image.getName(), response); writeImageDataBuffer(bufferedInputStream, getValidImageSize(image, response), response.getOutputStream()); } /** * sets content length and content type. * * @param size the size of the file * @param path the path to the image, used to extract the file extension * @param response the user response */ private void prepareImageResponse(final int size, final String path, final HttpServletResponse response) { response.setContentLength(size); response.setContentType("image/" + path.substring(path.lastIndexOf('.'))); } /** * Checks the image size and returns. * * @param image the image file * @param response the response * @return the size of the image, in bytes */ private int getValidImageSize(final File image, final HttpServletResponse response) { final long imageSize = image.length(); if (imageSize > Integer.MAX_VALUE) { // do something different response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); throw new StepInternalException("File is too big, not yet implemented: " + image.getAbsolutePath()); } return (int) imageSize; } /** * Write image data buffer. * * @param bufferedInputStream an input stream to the image * @param imageSize the size of the image * @param outputStream an output stream response * @throws IOException any exception while reading */ private void writeImageDataBuffer(final InputStream bufferedInputStream, final int imageSize, final OutputStream outputStream) throws IOException { final byte[] imageData = new byte[imageSize]; bufferedInputStream.read(imageData, 0, imageSize); outputStream.write(imageData); } }