org.ohmage.request.image.ImageReadRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.ohmage.request.image.ImageReadRequest.java

Source

/*******************************************************************************
 * Copyright 2012 The Regents of the University of California
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package org.ohmage.request.image;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.connector.ClientAbortException;
import org.apache.log4j.Logger;
import org.json.JSONObject;
import org.ohmage.annotator.Annotator.ErrorCode;
import org.ohmage.domain.Image;
import org.ohmage.exception.DomainException;
import org.ohmage.exception.InvalidRequestException;
import org.ohmage.exception.ServiceException;
import org.ohmage.exception.ValidationException;
import org.ohmage.request.InputKeys;
import org.ohmage.request.UserRequest;
import org.ohmage.service.ImageServices;
import org.ohmage.service.UserImageServices;
import org.ohmage.service.UserServices;
import org.ohmage.util.CookieUtils;
import org.ohmage.validator.ImageValidators;

/**
 * <p>Returns an image based on the given ID. The requester must be requesting
 * an image they created, a shared image in a campaign in which they are an
 * author, or a shared image in a shared campaign in which they are an analyst.
 * </p>
 * <table border="1">
 *   <tr>
 *     <td>Parameter Name</td>
 *     <td>Description</td>
 *     <td>Required</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#CLIENT}</td>
 *     <td>A string describing the client that is making this request.</td>
 *     <td>true</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#IMAGE_ID}</td>
 *     <td>The image's unique identifier.</td>
 *     <td>true</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#IMAGE_SIZE}</td>
 *     <td>If omitted, the originally uploaded image will be returned. If 
 *       given, it will alter the image in some way based on the value given. 
 *       It must be one of 
 *       {@link org.ohmage.validator.ImageValidators.ImageSize}.</td>
 *     <td>false</td>
 *   </tr>
 * </table>
 * 
 * @author John Jenkins
 */
public class ImageReadRequest extends UserRequest {
    private static final Logger LOGGER = Logger.getLogger(ImageReadRequest.class);

    private static final int CHUNK_SIZE = 4096;

    private final UUID imageId;
    private final Image.Size size;

    private Image image;

    /**
     * Creates a new image read request.
     * 
     * @param httpRequest The HttpServletRequest with all of the parameters to
     *                  build this request.
     * 
     * @throws InvalidRequestException Thrown if the parameters cannot be 
     *                            parsed.
     * 
     * @throws IOException There was an error reading from the request.
     */
    public ImageReadRequest(HttpServletRequest httpRequest) throws IOException, InvalidRequestException {
        super(httpRequest, false, TokenLocation.EITHER, null);

        UUID tImageId = null;
        Image.Size tSize = Image.ORIGINAL;

        if (!isFailed()) {
            LOGGER.info("Creating an image read request.");
            String[] t;

            try {
                t = getParameterValues(InputKeys.IMAGE_ID);
                if (t.length > 1) {
                    throw new ValidationException(ErrorCode.IMAGE_INVALID_ID,
                            "Multiple image IDs were given: " + InputKeys.IMAGE_ID);
                } else if (t.length == 0) {
                    throw new ValidationException(ErrorCode.IMAGE_INVALID_ID,
                            "The image ID is missing: " + InputKeys.IMAGE_ID);
                } else {
                    tImageId = ImageValidators.validateId(t[0]);

                    if (tImageId == null) {
                        throw new ValidationException(ErrorCode.IMAGE_INVALID_ID,
                                "The image ID is missing: " + InputKeys.IMAGE_ID);
                    }
                }

                t = getParameterValues(InputKeys.IMAGE_SIZE);
                if (t.length > 1) {
                    throw new ValidationException(ErrorCode.IMAGE_INVALID_SIZE,
                            "Multiple image sizes were given: " + InputKeys.IMAGE_SIZE);
                } else if (t.length == 1) {
                    tSize = ImageValidators.validateImageSize(t[0]);
                }
            } catch (ValidationException e) {
                e.failRequest(this);
                LOGGER.info(e.toString());
            }
        }

        imageId = tImageId;
        size = tSize;

        image = null;
    }

    /**
     * Services the request.
     */
    @Override
    public void service() {
        LOGGER.info("Servicing image read request.");

        if (!authenticate(AllowNewAccount.NEW_ACCOUNT_DISALLOWED)) {
            return;
        }

        try {
            LOGGER.info("Verifying that the image exists.");
            ImageServices.instance().verifyImageExistance(imageId, true);

            try {
                LOGGER.info("Checking if the user is an admin.");
                UserServices.instance().verifyUserIsAdmin(getUser().getUsername());
            } catch (ServiceException e) {
                LOGGER.info("Verifying that the user can read the image.");
                UserImageServices.instance().verifyUserCanReadImage(getUser().getUsername(), imageId);
            }

            LOGGER.info("Retrieving the original image.");
            image = ImageServices.instance().getImage(imageId, size);
        } catch (ServiceException e) {
            e.failRequest(this);
            e.logException(LOGGER);
        }
    }

    /**
     * Responds to the request with an image if it was successful or JSON if it
     * was not.
     */
    @Override
    public void respond(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        LOGGER.info("Writing the image read response.");

        if (ANDROID_CLIENT_NAME.equals(getClient())) {

            // TODO move to web server configuration for all image file types?
            // For now, this allows images to be stored by clients for one year
            // and specifies private to disable intermediaries from caching the
            // image. (HTTP 1.1)
            httpResponse.setHeader("Cache-Control", "max-age=1051200, private");

        } else {

            // Sets the HTTP headers to disable caching
            expireResponse(httpResponse);
        }

        // Open the connection to the image if it is not null.
        InputStream imageStream = null;
        try {
            if (image != null) {
                imageStream = image.getInputStream(size);
            }
        } catch (DomainException e) {
            LOGGER.error("Could not connect to the image.", e);
            this.setFailed(ErrorCode.SYSTEM_GENERAL_ERROR, "Image not found.");
            httpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
            super.respond(httpRequest, httpResponse, (JSONObject) null);
            return;
        }

        // If the request hasn't failed, attempt to write the file to the
        // output stream. 
        try {
            if (isFailed()) {
                httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                super.respond(httpRequest, httpResponse, (JSONObject) null);
            } else {
                // Set the type of the value.
                // FIXME: This isn't necessarily the case. We might want to do
                // some sort of image inspection to figure out what this should
                // be.
                httpResponse.setContentType(image.getType(size));
                httpResponse.setHeader("Content-Length", new Long(image.getSizeBytes(size)).toString());

                // If available, set the token.
                if (getUser() != null) {
                    final String token = getUser().getToken();
                    if (token != null) {
                        CookieUtils.setCookieValue(httpResponse, InputKeys.AUTH_TOKEN, token);
                    }
                }

                // Creates the writer that will write the response, success or 
                // fail.
                OutputStream os;
                try {
                    os = httpResponse.getOutputStream();
                } catch (IOException e) {
                    LOGGER.error("Unable to create writer object. Aborting.", e);
                    return;
                }

                // Set the output stream to the response.
                DataOutputStream dos = new DataOutputStream(os);
                byte[] bytes = new byte[CHUNK_SIZE];
                int currRead;
                try {
                    while ((currRead = imageStream.read(bytes)) != -1) {
                        dos.write(bytes, 0, currRead);
                    }
                } finally {
                    // Close the data output stream to which we were writing.
                    try {
                        dos.close();
                    } catch (ClientAbortException e) {
                        LOGGER.info("The client hung up unexpectedly.", e);
                    } catch (IOException e) {
                        LOGGER.warn("Error closing the data output stream.", e);
                    }
                }
            }
        }
        // If there was an error getting the image's information, abort
        // the whole operation and return an error.
        catch (DomainException e) {
            LOGGER.error("There was a problem reading or writing the image.", e);
            setFailed();
            httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        // If the client hangs up, just print a warning.
        catch (ClientAbortException e) {
            LOGGER.info("The client hung up unexpectedly.", e);
        }
        // If the error occurred while reading from the input stream or
        // writing to the output stream, abort the whole operation and
        // return an error.
        catch (IOException e) {
            LOGGER.error("The contents of the file could not be read or written to the response.", e);
            setFailed();
            httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        // No matter what, try to close the input stream if it exists.
        finally {
            try {
                if (imageStream != null) {
                    imageStream.close();
                }
            }
            // If the client hangs up, just print a warning.
            catch (ClientAbortException e) {
                LOGGER.info("The client hung up unexpectedly.", e);
            } catch (IOException e) {
                LOGGER.warn("Could not close the image stream.", e);
                // We don't care about failing the request, because, either, it
                // has already been failed or we wrote everything already.
            }
        }
    }
}