Java tutorial
/******************************************************************************* * 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.validator; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.xml.bind.DatatypeConverter; import org.joda.time.DateTime; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.ohmage.annotator.Annotator.ErrorCode; import org.ohmage.domain.Image; import org.ohmage.domain.campaign.SurveyResponse; import org.ohmage.domain.campaign.SurveyResponse.ColumnKey; import org.ohmage.domain.campaign.SurveyResponse.Function; import org.ohmage.domain.campaign.SurveyResponse.FunctionPrivacyStateItem; import org.ohmage.domain.campaign.SurveyResponse.OutputFormat; import org.ohmage.domain.campaign.SurveyResponse.SortParameter; import org.ohmage.exception.ValidationException; import org.ohmage.request.InputKeys; import org.ohmage.request.survey.SurveyResponseRequest; import org.ohmage.util.DateTimeUtils; import org.ohmage.util.StringUtils; /** * This class is responsible for validating survey response-based items. * * @author John Jenkins */ public final class SurveyResponseValidators { /** * Default constructor. Private so that it cannot be instantiated. */ private SurveyResponseValidators() { } /** * Validates a string representing a list of survey IDs. * * @param surveyIds The string list of survey IDs. * * @return A Set of unique survey IDs. */ public static Set<String> validateSurveyIds(final String surveyIds) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(surveyIds)) { return null; } Set<String> result = new HashSet<String>(); String[] surveyIdsArray = surveyIds.split(InputKeys.LIST_ITEM_SEPARATOR); for (int i = 0; i < surveyIdsArray.length; i++) { String currSurveyId = surveyIdsArray[i].trim(); if (!StringUtils.isEmptyOrWhitespaceOnly(currSurveyId)) { result.add(currSurveyId); } } return result; } /** * Validates a string representing a list of prompt response IDs. * * @param promptIds The string list of prompt IDs. * * @return A Set of unique prompt IDs. */ public static Set<String> validatePromptIds(final String promptIds) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(promptIds)) { return null; } Set<String> result = new HashSet<String>(); String[] promptIdsArray = promptIds.split(InputKeys.LIST_ITEM_SEPARATOR); for (int i = 0; i < promptIdsArray.length; i++) { String currPromptId = promptIdsArray[i].trim(); if (!StringUtils.isEmptyOrWhitespaceOnly(currPromptId)) { result.add(currPromptId); } } return result; } /** * Validates a string representing a map of survey IDs to a list of prompt * IDs for their respective surveys. * * @param promptIdMap The string map of survey IDs to their prompt IDs. * * @return A Map of survey IDs to prompt IDs. */ public static Map<String, Set<String>> validatePromptIdMap(final String promptIdMap) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(promptIdMap)) { return null; } // Get the array of JSON objects, where each object represents one // survey and its prompts. JSONArray surveysArray; try { surveysArray = new JSONArray(promptIdMap); } catch (JSONException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_PROMPT_MAP, "The survey-prompt ID map was not a valid JSON array: " + e.getMessage(), e); } // Create the result map. Map<String, Set<String>> result = new HashMap<String, Set<String>>(); // Cycle through the elements, processing them. int numSurveys = surveysArray.length(); for (int i = 0; i < numSurveys; i++) { // Get the survey object. JSONObject surveyObject; try { surveyObject = surveysArray.getJSONObject(i); } catch (JSONException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_PROMPT_MAP, "An element in the survey-prompt map was not an " + "object: " + e.getMessage(), e); } // Get the survey ID. String surveyId; try { surveyId = surveyObject.getString("survey_id"); } catch (JSONException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_PROMPT_MAP, "The survey ID was not a string: " + e.getMessage(), e); } // Get the array of prompt IDs. JSONArray promptIdsArray; try { promptIdsArray = surveyObject.getJSONArray("prompt_ids"); } catch (JSONException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_PROMPT_MAP, "The prompt ID list was not a JSON array: " + e.getMessage(), e); } // Create the Set and add the elements in the array to it. Set<String> promptIds = new HashSet<String>(); int numPromptIds = promptIdsArray.length(); for (int j = 0; j < numPromptIds; j++) { // Get the prompt ID. String promptId; try { promptId = promptIdsArray.getString(j); } catch (JSONException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_PROMPT_MAP, "The prompt ID is not a string: " + e.getMessage(), e); } // Validate the prompt ID. if (StringUtils.isEmptyOrWhitespaceOnly(promptId)) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_PROMPT_MAP, "The prompt ID is only whitespace."); } // Save the prompt ID. promptIds.add(promptId); } // Save the survey ID - prompt IDs collection to the result map. result.put(surveyId, promptIds); } // Return the result. return result; } /** * Validates that the provided survey response id is a valid UUID. * * @param surveyId A survey ID as a string to be validated. * * @return Returns the survey ID or null if it was null or whitespace. * * @throws ValidationException Thrown if the survey ID is not null, not * whitespace only, and not valid. */ public static UUID validateSurveyResponseId(final String surveyId) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(surveyId)) { return null; } // Surveys are only referenceable from the outside world using their // UUIDs. try { return UUID.fromString(surveyId); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_ID, "Invalid survey ID given: " + surveyId); } } /** * Validates that a list of survey response IDs are valid survey response * IDs. * * @param surveyResponseIds The list of survey response IDs as a string to * be validated. * * @return A set of survey response IDs. * * @throws ValidationException The survey response ID list was not null, * not whitespace only, and one or more of the * IDs was not valid. */ public static Set<UUID> validateSurveyResponseIds(final String surveyResponseIds) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(surveyResponseIds)) { return null; } String[] surveyResponseIdsArray = surveyResponseIds.split(InputKeys.LIST_ITEM_SEPARATOR); Set<UUID> result = new HashSet<UUID>(surveyResponseIdsArray.length); for (int i = 0; i < surveyResponseIdsArray.length; i++) { UUID currId = validateSurveyResponseId(surveyResponseIdsArray[i]); if (currId != null) { result.add(currId); } } return result; } /** * Validates that a privacy state is a valid survey response privacy state. * * @param privacyState The privacy state to be validated. * * @return Returns null if the privacy state is null or whitespace only; * otherwise, the privacy state is returned. * * @throws ValidationException Thrown if the privacy state is not null, not * whitespace only, and not a valid survey * response privacy state. */ public static SurveyResponse.PrivacyState validatePrivacyState(final String privacyState) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(privacyState)) { return null; } try { return SurveyResponse.PrivacyState.getValue(privacyState); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_PRIVACY_STATE, "The privacy state is unknown: " + privacyState); } } /** * Validates that a set of usernames is either the special URN value or is * a valid list of usernames. * * @param usernames The usernames list as a string. * * @return A set of usernames that may only contain the special URN value, * or null if the usernames string is null or whitespace only. * * @throws ValidationException Thrown if the usernames string listcontains * a username that is invalid. */ public static Set<String> validateUsernames(final String usernames) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(usernames)) { return null; } String[] usernamesArray = usernames.split(InputKeys.LIST_ITEM_SEPARATOR); for (int i = 0; i < usernamesArray.length; i++) { if (SurveyResponseRequest.URN_SPECIAL_ALL.equals(usernamesArray[i])) { Set<String> result = new HashSet<String>(1); result.add(SurveyResponseRequest.URN_SPECIAL_ALL); return result; } } return UserValidators.validateUsernames(usernames); } /** * Validates that the column list string contains only valid column keys or * none at all. * * @param columnList The column list as a string. * * @return Null if the column list string is null or whitespace only; * otherwise, a, possibly empty, list of column keys is returned. * If the special all keys key is given, the resulting collection * will contain all of the known keys. * * @throws ValidationException Thrown if an unknown key is found. */ public static Set<SurveyResponse.ColumnKey> validateColumnList(final String columnList) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(columnList)) { return null; } Set<SurveyResponse.ColumnKey> result = new HashSet<SurveyResponse.ColumnKey>(); // Split the list into the individual items and cycle through them. String[] columnListArray = columnList.split(InputKeys.LIST_ITEM_SEPARATOR); for (int i = 0; i < columnListArray.length; i++) { // Sometimes the split function parses out empty strings, so we // will ignore those. if (!StringUtils.isEmptyOrWhitespaceOnly(columnListArray[i])) { // Get the current non-null, non-empty string value. String currValue = columnListArray[i].trim(); // Attempt to parse it into a known column key and add it. try { result.add(SurveyResponse.ColumnKey.getValue(currValue)); } catch (IllegalArgumentException e) { // If the column key is unknown, check if it is the special // key that represents all columns. if (SurveyResponseRequest.URN_SPECIAL_ALL.equals(currValue)) { // It is the special key, so add all of the known // column keys and return the result. ColumnKey[] allKeys = SurveyResponse.ColumnKey.values(); for (int j = 0; j < allKeys.length; j++) { result.add(allKeys[j]); } return result; } // It is not the special key and wasn't a known key, so // throw an exception. else { throw new ValidationException(ErrorCode.SURVEY_MALFORMED_COLUMN_LIST, "The column list contains an unknown value: " + currValue, e); } } } } return result; } /** * Validates that a string value represents a known output format value. * * @param outputFormat The output format as a string. * * @return An OutputFormat object, or null if the output format string was * null or whitespace only. * * @throws ValidationException Thrown if the output format is unknown. */ public static OutputFormat validateOutputFormat(final String outputFormat) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(outputFormat)) { return null; } try { return OutputFormat.getValue(outputFormat); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_OUTPUT_FORMAT, "The output format is unknown: " + outputFormat); } } /** * Validates that a list of sort order values contains all of the required * values with no duplicates or is empty. * * @param sortOrder The sort order values as a string. * * @return An ordered list of SortParameter representing the sort * parameters in their required order. * * @throws ValidationException Thrown if an unknown sort order value is * given, if the same sort order value is given * multiple times, or if all of the known sort * order values weren't given. */ public static List<SortParameter> validateSortOrder(final String sortOrder) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(sortOrder)) { return null; } List<SortParameter> result = new ArrayList<SortParameter>(3); String[] sortOrderArray = sortOrder.split(InputKeys.LIST_ITEM_SEPARATOR); for (int i = 0; i < sortOrderArray.length; i++) { SortParameter currSortParameter; try { currSortParameter = SortParameter.getValue(sortOrderArray[i].trim()); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SORT_ORDER, "An unknown sort order value was given: " + sortOrderArray[i]); } if (result.contains(currSortParameter)) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SORT_ORDER, "The same sort order value was given multiple times: " + currSortParameter.toString()); } else { result.add(currSortParameter); } } if (result.size() != SortParameter.values().length) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SORT_ORDER, "There are " + SortParameter.values().length + " sort order values and " + result.size() + " were given."); } return result; } /** * Validates that a function ID is a known function ID and returns it. * * @param function The function ID as a string. * * @return The decoded function ID as a Function or null if the string was * null or whitespace only. * * @throws ValidationException Thrown if the function was not null, not * whitespace only, and not a valid function * ID. */ public static Function validateFunction(final String function) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(function)) { return null; } try { return Function.valueOf(function.toUpperCase()); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_SURVEY_FUNCTION_ID, "The survey response function ID is unknown: " + function); } } /** * Validates a list of privacy state grouping items. * * @param list The list of grouping items as a string. * * @return The decoded set of grouping items. * * @throws ValidationException Thrown if one of the items in the list * wasn't decodable. */ public static Set<FunctionPrivacyStateItem> validatePrivacyStateGroupList(final String list) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(list)) { return Collections.emptySet(); } String[] listArray = list.split(InputKeys.LIST_ITEM_SEPARATOR); int numItems = listArray.length; Set<FunctionPrivacyStateItem> result = new HashSet<FunctionPrivacyStateItem>(numItems); for (int i = 0; i < listArray.length; i++) { String item = listArray[i]; if ((item != null) && (!StringUtils.isEmptyOrWhitespaceOnly(item))) { try { result.add(FunctionPrivacyStateItem.getValue(item)); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_FUNCTION_INVALID_PRIVACY_STATE_GROUP_ITEM, "The function grouping item is unknown.", e); } } } return result; } /** * Validates that a date is a valid Date and returns it. * * @param startDate The start date to be validated and decoded into a Date * object. * * @return Returns null if the start date was null or whitespace only. * Otherwise, it returns the decoded Date. * * @throws ValidationException Thrown if the start date was not null, not * whitespace only, and not a valid date. */ public static DateTime validateStartDate(final String startDate) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(startDate)) { return null; } try { return DateTimeUtils.getDateTimeFromString(startDate); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SERVER_INVALID_DATE, "The date is not valid: " + startDate); } } /** * Validates that a date is a valid Date and returns it. * * @param endDate The end date to be validated and decoded into a Date * object. * * @return Returns null if the end date was null or whitespace only. * Otherwise, it returns the decoded Date. * * @throws ValidationException Thrown if the end date was not null, not * whitespace only, and not a valid date. */ public static DateTime validateEndDate(final String endDate) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(endDate)) { return null; } try { return DateTimeUtils.getDateTimeFromString(endDate); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SERVER_INVALID_DATE, "The date is not valid: " + endDate); } } /** * Validates the optional suppressMetadata boolean. * * @param suppressMetadata The value to validate. * @return the Boolean equivalent of suppressMetadata * @throws ValidationException if suppressMetadata is not null and non-boolean. * @throws IllegalArgumentException if the request is null */ public static Boolean validateSuppressMetadata(final String suppressMetadata) throws ValidationException { return validateOptionalBoolean(suppressMetadata, ErrorCode.SURVEY_INVALID_SUPPRESS_METADATA_VALUE, "The suppress metadata value is invalid: "); } /** * Validates the optional returnId boolean. * * @param returnId The value to validate. * @return the Boolean equivalent of returnId * @throws ValidationException if returnId is not null and non-boolean. */ public static Boolean validateReturnId(final String returnId) throws ValidationException { return validateOptionalBoolean(returnId, ErrorCode.SURVEY_INVALID_RETURN_ID, "The return ID value is invalid: "); } /** * Validates the optional prettyPrint boolean. * * @param returnId The value to validate. * @return the Boolean equivalent of prettyPrint * @throws ValidationException if prettyPrint is not null and non-boolean. */ public static Boolean validatePrettyPrint(final String prettyPrint) throws ValidationException { return validateOptionalBoolean(prettyPrint, ErrorCode.SURVEY_INVALID_PRETTY_PRINT_VALUE, "The pretty print value is invalid: "); } /** * Validates the optional collapse boolean. * * @param returnId The value to validate. * @return the Boolean equivalent of collapse * @throws ValidationException if collapse is not null and non-boolean. */ public static Boolean validateCollapse(final String collapse) throws ValidationException { return validateOptionalBoolean(collapse, ErrorCode.SURVEY_INVALID_COLLAPSE_VALUE, "The collapse value is invalid: "); } /** * Validates the number of survey responses to skip. * * @param surveyResponsesToSkip The value to be validated. * * @return The number of rows to skip as given by the user or the default * value 0. * * @throws ValidationException Thrown if the value cannot be parsed or is * negative. */ public static long validateNumSurveyResponsesToSkip(final String surveyResponsesToSkip) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(surveyResponsesToSkip)) { return 0; } try { long value = Long.decode(surveyResponsesToSkip); if (value < 0) { throw new ValidationException(ErrorCode.SERVER_INVALID_NUM_TO_SKIP, "The number of survy responses cannot be negative: " + value); } return value; } catch (NumberFormatException e) { throw new ValidationException(ErrorCode.SERVER_INVALID_NUM_TO_SKIP, "The number of survey responses to skip was not a number.", e); } } /** * Validates the number of survey responses to process. * * @param surveyResponsesToProcess The value to be validated. * * @param limit The maximum allowed value. * * @return The number of rows to analyze as given by the user or the * default value which is the parameterized limit. * * @throws ValidationException Thrown if the value cannot be parsed. */ public static long validateNumSurveyResponsesToProcess(final String surveyResponsesToProcess, final long limit) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(surveyResponsesToProcess)) { return limit; } try { long value = Long.decode(surveyResponsesToProcess); if (value > limit) { throw new ValidationException(ErrorCode.SERVER_INVALID_NUM_TO_RETURN, "The number of survey responses to process was larger than the limit: " + limit); } return value; } catch (NumberFormatException e) { throw new ValidationException(ErrorCode.SERVER_INVALID_NUM_TO_RETURN, "The number of survey responses to process was not a number.", e); } } /** * Utility for validating optional booleans where booleans must adhere to * the strict values of "true" or "false" if the booleanString is not null. * * @param booleanString The string to validate. * @param errorCode The error code to use when failing the request. * @param errorMessage The error message to use when failing the request. * @return A valid Boolean or null * @throws ValidationException if the booleanString cannot be strictly * decoded to "true" or "false" * @throws IllegalArgumentException if the request is null; if the error * code is empty or null; or if the error message is empty or null. */ private static Boolean validateOptionalBoolean(final String booleanString, final ErrorCode errorCode, final String errorMessage) throws ValidationException { // don't validate the optional value if it doesn't exist if (StringUtils.isEmptyOrWhitespaceOnly(booleanString)) { return null; } // perform validation if (StringUtils.decodeBoolean(booleanString) == null) { throw new ValidationException(errorCode, errorMessage + booleanString); } return Boolean.valueOf(booleanString); } /** * Validates that a "images" value was a valid JSON object whose keys were * the image IDs (UUIDs) and values were BASE64-encoded images. It then * returns the map of the IDs to BufferedImages. * * @param value The value to be validated. * * @return The map of image IDs to Images. * * @throws ValidationException The value was not valid JSON, an image's ID * was not a valid UUID, or an image's contents * was not a valid BASE64-encoded image. */ public static Map<UUID, Image> validateImages(final String value) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(value)) { return null; } JSONObject imagesJson; try { imagesJson = new JSONObject(value); } catch (JSONException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_IMAGES_VALUE, "The images parameter was not valid JSON: " + value, e); } Map<UUID, Image> results = new HashMap<UUID, Image>(); Iterator<?> imageIds = imagesJson.keys(); int numImageIds = imagesJson.length(); for (int i = 0; i < numImageIds; i++) { UUID imageUuid; String imageId = (String) imageIds.next(); try { imageUuid = UUID.fromString(imageId); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_IMAGES_VALUE, "An image's ID is not a valid UUID: " + imageId, e); } Image image; try { image = ImageValidators.validateImageContents(imageUuid, DatatypeConverter.parseBase64Binary(imagesJson.getString(imageId))); } catch (JSONException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_IMAGES_VALUE, "The image's contents in the JSON was not a string.", e); } catch (IllegalArgumentException e) { throw new ValidationException(ErrorCode.SURVEY_INVALID_IMAGES_VALUE, "The image's contents were not BASE64.", e); } results.put(imageUuid, image); } return results; } /** * Compiles a set of strings from a whitespace-deliminated search string. * * @param searchString The string to be tokenized. * * @return The set of tokens from the search string. * * @throws ValidationException Never thrown. */ public static Set<String> validatePromptResponseSearch(final String searchString) throws ValidationException { if (StringUtils.isEmptyOrWhitespaceOnly(searchString)) { return null; } return StringUtils.decodeSearchString(searchString); } }