org.ohmage.request.survey.SurveyUploadRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.ohmage.request.survey.SurveyUploadRequest.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.survey;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

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

import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.json.JSONObject;
import org.ohmage.annotator.Annotator.ErrorCode;
import org.ohmage.domain.Audio;
import org.ohmage.domain.Image;
import org.ohmage.domain.Video;
import org.ohmage.domain.campaign.Campaign;
import org.ohmage.domain.campaign.SurveyResponse;
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.CampaignServices;
import org.ohmage.service.SurveyResponseServices;
import org.ohmage.service.UserCampaignServices;
import org.ohmage.util.DateTimeUtils;
import org.ohmage.validator.CampaignValidators;
import org.ohmage.validator.ImageValidators;
import org.ohmage.validator.SurveyResponseValidators;

/**
 * <p>Stores a survey and its associated images (if any are present in the payload)</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#USERNAME}</td>
 *     <td>The username of the uploader.</td>
 *     <td>true</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#PASSWORD}</td>
 *     <td>The password for the associated username/</td>
 *     <td>true</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#CAMPAIGN_URN}</td>
 *     <td>The campaign URN for the survey(s) being uploaded.</td>
 *     <td>true</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#CAMPAIGN_CREATION_TIMESTAMP}</td>
 *     <td>The creation timestamp for the campaign. This parameter is used to
 *     ensure that the client's campaign is up-to-date.</td>
 *     <td>true</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#SURVEYS}</td>
 *     <td>The survey data payload for the survey(s) being uploaded.</td>
 *     <td>true</td>
 *   </tr>
 *   <tr>
 *     <td>{@value org.ohmage.request.InputKeys#IMAGES}</td>
 *     <td>A JSON object where the keys are the image IDs and the values are 
 *       the images' contents BASE64-encoded.</td>
 *     <td>Either this or the deprecated imageId/imageContents 
 *       multipart/form-post method must define all images in the payload.</td>
 *   </tr>
 *   <tr>
 *     <td>The image's ID.</td>
 *     <td>The image's constants.</td>
 *     <td>One for every image in the payload. This is deprecated in favor of
 *       the {@value org.ohmage.request.InputKeys#IMAGES} parameter.</td>
 *   </tr>
 * </table>
 * 
 * @author Joshua Selsky
 */
public class SurveyUploadRequest extends UserRequest {
    private static final Logger LOGGER = Logger.getLogger(SurveyUploadRequest.class);

    // The campaign creation timestamp is stored as a String because it is 
    // never used in any kind of calculation.
    private final String campaignUrn;
    private final DateTime campaignCreationTimestamp;
    private List<JSONObject> jsonData;
    private final Map<UUID, Image> imageContentsMap;
    private final Map<String, Video> videoContentsMap;
    private final Map<String, Audio> audioContentsMap;
    private final String owner;

    private Collection<UUID> surveyResponseIds;

    public SurveyUploadRequest(final HttpServletRequest httpRequest, final Map<String, String[]> parameters,
            final String campaignId, final String data, final String owner)
            throws IOException, InvalidRequestException {

        super(httpRequest, false, TokenLocation.PARAMETER, parameters);

        String tCampaignUrn = null;
        List<JSONObject> tJsonData = null;

        if (!isFailed()) {
            LOGGER.info("Creating a survey response upload request.");

            try {
                if (campaignId == null) {
                    throw new ValidationException(ErrorCode.CAMPAIGN_INVALID_ID, "The campaign ID was null.");
                }
                if (data == null) {
                    throw new ValidationException(ErrorCode.SURVEY_INVALID_RESPONSES,
                            "The list of survey responses was null.");
                }

                tCampaignUrn = CampaignValidators.validateCampaignId(campaignId);
                tJsonData = CampaignValidators.validateUploadedJson(data);
            } catch (ValidationException e) {
                e.failRequest(this);
                e.logException(LOGGER);
            }
        }

        campaignUrn = tCampaignUrn;
        campaignCreationTimestamp = null;
        jsonData = tJsonData;
        imageContentsMap = Collections.emptyMap();
        videoContentsMap = Collections.emptyMap();
        audioContentsMap = Collections.emptyMap();
        this.owner = owner;
    }

    /**
     * Creates a new survey upload request.
     * 
     * @param httpRequest The HttpServletRequest with the parameters for this
     *                  request.
     * 
     * @throws InvalidRequestException Thrown if the parameters cannot be 
     *                            parsed.
     * 
     * @throws IOException There was an error reading from the request.
     */
    public SurveyUploadRequest(HttpServletRequest httpRequest) throws IOException, InvalidRequestException {
        super(httpRequest, false, TokenLocation.PARAMETER, null);

        LOGGER.info("Creating a survey upload request.");

        String tCampaignUrn = null;
        DateTime tCampaignCreationTimestamp = null;
        List<JSONObject> tJsonData = null;
        Map<UUID, Image> tImageContentsMap = null;
        Map<String, Video> tVideoContentsMap = null;
        Map<String, Audio> tAudioContentsMap = null;

        if (!isFailed()) {
            try {
                Map<String, String[]> parameters = getParameters();

                // Validate the campaign URN
                String[] t = parameters.get(InputKeys.CAMPAIGN_URN);
                if (t == null || t.length != 1) {
                    throw new ValidationException(ErrorCode.CAMPAIGN_INVALID_ID,
                            "campaign_urn is missing or there is more than one.");
                } else {
                    tCampaignUrn = CampaignValidators.validateCampaignId(t[0]);

                    if (tCampaignUrn == null) {
                        throw new ValidationException(ErrorCode.CAMPAIGN_INVALID_ID, "The campaign ID is invalid.");
                    }
                }

                // Validate the campaign creation timestamp
                t = parameters.get(InputKeys.CAMPAIGN_CREATION_TIMESTAMP);
                if (t == null || t.length != 1) {
                    throw new ValidationException(ErrorCode.SERVER_INVALID_TIMESTAMP,
                            "campaign_creation_timestamp is missing or there is more than one");
                } else {

                    // Make sure it's a valid timestamp.
                    try {
                        tCampaignCreationTimestamp = DateTimeUtils.getDateTimeFromString(t[0]);
                    } catch (IllegalArgumentException e) {
                        throw new ValidationException(ErrorCode.SERVER_INVALID_DATE, e.getMessage(), e);
                    }
                }

                byte[] surveyDataBytes = getParameter(httpRequest, InputKeys.SURVEYS);
                if (surveyDataBytes == null) {
                    throw new ValidationException(ErrorCode.SURVEY_INVALID_RESPONSES,
                            "No value found for 'surveys' parameter or multiple surveys parameters were found.");
                } else {
                    LOGGER.debug(new String(surveyDataBytes));
                    try {
                        tJsonData = CampaignValidators.validateUploadedJson(new String(surveyDataBytes, "UTF-8"));
                    } catch (IllegalArgumentException e) {
                        throw new ValidationException(ErrorCode.SURVEY_INVALID_RESPONSES,
                                "The survey responses could not be URL decoded.", e);
                    }
                }

                tImageContentsMap = new HashMap<UUID, Image>();
                t = getParameterValues(InputKeys.IMAGES);
                if (t.length > 1) {
                    throw new ValidationException(ErrorCode.SURVEY_INVALID_IMAGES_VALUE,
                            "Multiple images parameters were given: " + InputKeys.IMAGES);
                } else if (t.length == 1) {
                    LOGGER.debug("Validating the BASE64-encoded images.");
                    Map<UUID, Image> images = SurveyResponseValidators.validateImages(t[0]);

                    if (images != null) {
                        tImageContentsMap.putAll(images);
                    }
                }

                // Retrieve and validate images and videos.
                List<UUID> imageIds = new ArrayList<UUID>();
                tVideoContentsMap = new HashMap<String, Video>();
                tAudioContentsMap = new HashMap<String, Audio>();
                Collection<Part> parts = null;
                try {
                    // FIXME - push to base class especially because of the ServletException that gets thrown
                    parts = httpRequest.getParts();
                    for (Part p : parts) {
                        UUID id;
                        String name = p.getName();
                        try {
                            id = UUID.fromString(name);
                        } catch (IllegalArgumentException e) {
                            LOGGER.info("Ignoring part: " + name);
                            continue;
                        }

                        String contentType = p.getContentType();
                        if (contentType.startsWith("image")) {
                            imageIds.add(id);
                        } else if (contentType.startsWith("video/")) {
                            tVideoContentsMap.put(name, new Video(UUID.fromString(name), contentType.split("/")[1],
                                    getMultipartValue(httpRequest, name)));
                        } else if (contentType.startsWith("audio/")) {
                            try {
                                tAudioContentsMap.put(name, new Audio(UUID.fromString(name),
                                        contentType.split("/")[1], getMultipartValue(httpRequest, name)));
                            } catch (DomainException e) {
                                throw new ValidationException(ErrorCode.SYSTEM_GENERAL_ERROR,
                                        "Could not create the Audio object.", e);
                            }
                        }
                    }
                } catch (ServletException e) {
                    LOGGER.info("This is not a multipart/form-post.");
                } catch (IOException e) {
                    LOGGER.error("cannot parse parts", e);
                    throw new ValidationException(e);
                } catch (DomainException e) {
                    LOGGER.info("A Media object could not be built.", e);
                    throw new ValidationException(e);
                }

                Set<UUID> stringSet = new HashSet<UUID>(imageIds);

                if (stringSet.size() != imageIds.size()) {
                    throw new ValidationException(ErrorCode.IMAGE_INVALID_DATA,
                            "a duplicate image key was detected in the multi-part upload");
                }

                for (UUID imageId : imageIds) {
                    Image image = ImageValidators.validateImageContents(imageId,
                            getMultipartValue(httpRequest, imageId.toString()));
                    if (image == null) {
                        throw new ValidationException(ErrorCode.IMAGE_INVALID_DATA,
                                "The image data is missing: " + imageId);
                    }
                    tImageContentsMap.put(imageId, image);

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("succesfully created a BufferedImage for key " + imageId);
                    }
                }
            } catch (ValidationException e) {
                e.failRequest(this);
                e.logException(LOGGER, true);
            }
        }

        this.campaignUrn = tCampaignUrn;
        this.campaignCreationTimestamp = tCampaignCreationTimestamp;
        this.jsonData = tJsonData;
        this.imageContentsMap = tImageContentsMap;
        this.videoContentsMap = tVideoContentsMap;
        this.audioContentsMap = tAudioContentsMap;
        this.owner = null;

        surveyResponseIds = null;
    }

    /**
     * Services the request.
     */
    @Override
    public void service() {
        LOGGER.info("Servicing a survey upload request.");

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

        try {
            LOGGER.info("Verifying that the user is a participant in the campaign.");
            UserCampaignServices.instance().verifyUserCanUploadSurveyResponses(getUser().getUsername(),
                    campaignUrn);

            LOGGER.info("Verifying that the campaign is running.");
            CampaignServices.instance().verifyCampaignIsRunning(campaignUrn);

            Collection<String> campaignIds = new ArrayList<String>(1);
            campaignIds.add(campaignUrn);
            LOGGER.info("Generating the campaign object.");
            Map<Campaign, Collection<Campaign.Role>> campaigns = UserCampaignServices.instance()
                    .getCampaignInformation(getUser().getUsername(), campaignIds, null, null, null, null, null,
                            null, null, null, false, false);
            if (campaigns.size() == 0) {
                throw new ServiceException(ErrorCode.CAMPAIGN_INVALID_ID,
                        "The campaign does not exist: " + campaignUrn);
            }
            if (campaigns.size() > 1) {
                throw new ServiceException("Multiple campaigns exist with the same ID.");
            }
            Campaign campaign = campaigns.keySet().iterator().next();

            if (campaignCreationTimestamp != null) {
                LOGGER.info("Verifying that the uploaded survey responses aren't out of date.");
                CampaignServices.instance().verifyCampaignIsUpToDate(campaign, campaignCreationTimestamp);
            }

            LOGGER.info("Verifying the uploaded data against the campaign.");
            List<SurveyResponse> surveyResponses = CampaignServices.instance()
                    .getSurveyResponses(getUser().getUsername(), getClient(), campaign, jsonData);

            surveyResponseIds = new ArrayList<UUID>(surveyResponses.size());
            for (SurveyResponse surveyResponse : surveyResponses) {
                surveyResponseIds.add(surveyResponse.getSurveyResponseId());
            }

            LOGGER.info("Validating that all photo prompt responses have their corresponding images attached.");
            SurveyResponseServices.instance().verifyImagesExistForPhotoPromptResponses(surveyResponses,
                    imageContentsMap);

            LOGGER.info("Validating that all video prompt responses have their corresponding videos attached.");
            SurveyResponseServices.instance().verifyVideosExistForVideoPromptResponses(surveyResponses,
                    videoContentsMap);

            LOGGER.info(
                    "Validating that all audio prompt responses have their corresponding audio files attached.");
            SurveyResponseServices.instance().verifyAudioFilesExistForAudioPromptResponses(surveyResponses,
                    audioContentsMap);

            LOGGER.info("Inserting " + surveyResponses.size() + " survey responses into the database.");
            List<Integer> duplicateIndexList = SurveyResponseServices.instance().createSurveyResponses(
                    ((owner == null) ? getUser().getUsername() : owner), getClient(), campaignUrn, surveyResponses,
                    imageContentsMap, videoContentsMap, audioContentsMap);

            LOGGER.info("Found " + duplicateIndexList.size() + " duplicate survey uploads");
        } catch (ServiceException e) {
            e.failRequest(this);
            e.logException(LOGGER, true);
        }
    }

    /**
     * Responds to the image upload request with success or a failure message
     * that contains a failure code and failure text.
     */
    @Override
    public void respond(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        LOGGER.info("Responding to the survey upload request.");

        super.respond(httpRequest, httpResponse, (JSONObject) null);
    }

    /**
     * If the upload was successful, this records the UUIDs of the successfully
     * uploaded survey responses to the audit's extras table.
     * 
     * @return The parents audit information with the successfully uploaded
     *          survey responses if the request was successful.
     */
    @Override
    public Map<String, String[]> getAuditInformation() {
        Map<String, String[]> result = super.getAuditInformation();

        if ((!isFailed()) && (surveyResponseIds != null)) {
            int numSurveyResponseIdsAdded = 0;
            String[] surveyResponseIdsArray = new String[surveyResponseIds.size()];

            for (UUID surveyResponseId : surveyResponseIds) {
                surveyResponseIdsArray[numSurveyResponseIdsAdded] = surveyResponseId.toString();
                numSurveyResponseIdsAdded++;
            }

            result.put("successfully_uploaded_survey_response_ids", surveyResponseIdsArray);
        }

        return result;
    }
}