/* * Copyright (C) 2015-2017 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * * Akvo FLOW is free software: you can redistribute it and modify it under the terms of * the GNU Affero General Public License (AGPL) as published by the Free Software Foundation, * either version 3 of the License or any later version. * * Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License included below for more details. * * The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>. */ package org.waterforpeople.mapping.serialization; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.map.ObjectMapper; import org.waterforpeople.mapping.domain.QuestionAnswerStore; import org.waterforpeople.mapping.domain.SurveyInstance; import org.waterforpeople.mapping.domain.response.FormInstance; import org.waterforpeople.mapping.domain.response.Response; import com.gallatinsystems.survey.dao.QuestionDao; import com.gallatinsystems.survey.domain.Question; public class SurveyInstanceHandler { private static final Logger log = Logger.getLogger(SurveyInstanceHandler.class.getName()); /** * TSV token indexes. For historical/legacy reasons, many indexes are empty or skipped. */ private static final int SURVEY_ID = 0; private static final int QUESTION_ID = 2; private static final int ANSWER_TYPE = 3; private static final int ANSWER_VALUE = 4; private static final int USERNAME = 5; private static final int COLLECTION_DATE = 7; private static final int DEVICE_ID = 8; private static final int UUID = 11; private static final int DURATION = 12; private static final int DATAPOINT_ID = 13; public static SurveyInstance fromJSON(String data) { FormInstance formInstance = null; ObjectMapper mapper = new ObjectMapper(); try { formInstance = mapper.readValue(data, FormInstance.class); } catch (IOException e) { log.log(Level.SEVERE, "Error mapping JSON data: " + e.getMessage(), e); return null; } SurveyInstance si = new SurveyInstance(); si.setUserID(1L); si.setCollectionDate(new Date(formInstance.getSubmissionDate())); si.setSubmitterName(formInstance.getUsername()); si.setDeviceIdentifier(formInstance.getDeviceId()); si.setSurveyalTime(formInstance.getDuration()); si.setSurveyId(retrieveFormId(formInstance)); si.setSurveyedLocaleIdentifier(formInstance.getDataPointId()); si.setUuid(formInstance.getUUID()); si.setQuestionAnswersStore(new ArrayList<QuestionAnswerStore>()); // Process form responses for (Response response : formInstance.getResponses()) { QuestionAnswerStore qas = new QuestionAnswerStore(); qas.setSurveyId(si.getSurveyId()); qas.setQuestionID(response.getQuestionId()); qas.setCollectionDate(si.getCollectionDate()); qas.setType(response.getAnswerType()); qas.setValue(response.getValue()); qas.setIteration(response.getIteration()); // If one of the answer types is META_GEO or META_NAME, set up // the surveyedLocale corresponding attribute, and skip QAS if ("META_NAME".equals(qas.getType())) { si.setSurveyedLocaleDisplayName(qas.getValue()); } else if ("META_GEO".equals(qas.getType())) { si.setLocaleGeoLocation(qas.getValue()); } else { si.getQuestionAnswersStore().add(qas); } } return si; } /* * Retrieve the formId for form data coming in via data.json files. This extra check for the * formId has been created in order to handle a bug where the formId was returned as an * arbitrary string. We ensure that the formId is strictly a long otherwise we try to retrieve * it through the questionId values that come with each individual question response. See * https://github.com/akvo/akvo-flow-mobile/issues/614 */ private static Long retrieveFormId(FormInstance formInstance) { if (StringUtils.isNotBlank(formInstance.getFormId()) && StringUtils.isNumeric(formInstance.getFormId())) { return Long.parseLong(formInstance.getFormId()); } for (Response response : formInstance.getResponses()) { Long formId = retrieveFormIdByQuestionId(response.getQuestionId()); if (formId != null) { return formId; } } return null; } /* * Retrieve the formId from the corresponding Question entity in the datastore */ private static Long retrieveFormIdByQuestionId(String questionId) { if (questionId == null || questionId.trim().isEmpty()) { return null; } Question question = new QuestionDao().getByKey(Long.parseLong(questionId)); if (question == null) { return null; } return question.getSurveyId(); } public static SurveyInstance fromTSV(List<String> data) { final SurveyInstance si = new SurveyInstance(); si.setUserID(1L); si.setQuestionAnswersStore(new ArrayList<QuestionAnswerStore>()); boolean first = true; for (String line : data) { final String[] parts = line.split("\t"); if (parts.length < UUID + 1) { return null; } if (first) { try { si.setSurveyId(retrieveFormId(parts)); si.setCollectionDate(new Date(new Long(parts[COLLECTION_DATE].trim()))); } catch (NumberFormatException e) { log.log(Level.SEVERE, "Could not parse line: " + line, e); return null; } si.setSubmitterName(parts[USERNAME].trim()); si.setDeviceIdentifier(parts[DEVICE_ID].trim()); si.setUuid(parts[UUID].trim()); // Time and LocaleID. Old app versions might not include these columns. if (parts.length > DURATION) { try { si.setSurveyalTime(Long.valueOf(parts[DURATION].trim())); } catch (NumberFormatException e) { log.log(Level.WARNING, "Surveyal time column is not a number", e); } } if (parts.length > DATAPOINT_ID) { si.setSurveyedLocaleIdentifier(parts[DATAPOINT_ID].trim()); } first = false; } QuestionAnswerStore qas = new QuestionAnswerStore(); qas.setSurveyId(si.getSurveyId()); qas.setQuestionID(parts[QUESTION_ID].trim()); qas.setType(parts[ANSWER_TYPE].trim()); qas.setCollectionDate(si.getCollectionDate()); qas.setValue(parts[ANSWER_VALUE].trim()); // If one of the answer types is META_GEO or META_NAME, set up // the surveyedLocale corresponding attribute, and skip QAS if ("META_NAME".equals(qas.getType())) { si.setSurveyedLocaleDisplayName(qas.getValue()); } else if ("META_GEO".equals(qas.getType())) { si.setLocaleGeoLocation(qas.getValue()); } else { si.getQuestionAnswersStore().add(qas); } } return si; } /* * Parse formId for form data coming in via data.txt files. This extra check for the formId has * been created in order to handle a bug where the formId was returned as an arbitrary string. * We ensure that the formId is strictly a long otherwise we try to retrieve it through the * questionId values that come with each individual question response. See * https://github.com/akvo/akvo-flow-mobile/issues/614 */ private static Long retrieveFormId(String[] parts) { if (StringUtils.isNumeric(parts[SURVEY_ID].trim())) { return Long.parseLong(parts[SURVEY_ID].trim()); } else { return retrieveFormIdByQuestionId(parts[QUESTION_ID].trim()); } } }