/* * Copyright (C) 2012-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.app.web.rest; import com.gallatinsystems.metric.dao.SurveyMetricMappingDao; import com.gallatinsystems.metric.domain.SurveyMetricMapping; import com.gallatinsystems.survey.dao.QuestionDao; import com.gallatinsystems.survey.dao.QuestionOptionDao; import com.gallatinsystems.survey.dao.SurveyDAO; import com.gallatinsystems.survey.dao.SurveyGroupDAO; import com.gallatinsystems.survey.dao.SurveyUtils; import com.gallatinsystems.survey.domain.Question; import com.gallatinsystems.survey.domain.QuestionOption; import com.gallatinsystems.survey.domain.Survey; import com.gallatinsystems.survey.domain.SurveyGroup; import com.gallatinsystems.surveyal.dao.SurveyalValueDao; import com.google.appengine.api.taskqueue.QueueFactory; import com.google.appengine.api.taskqueue.TaskOptions; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.waterforpeople.mapping.app.gwt.client.survey.QuestionDto; import org.waterforpeople.mapping.app.gwt.client.survey.QuestionDtoMapper; import org.waterforpeople.mapping.app.gwt.client.survey.QuestionOptionDto; import org.waterforpeople.mapping.app.gwt.client.survey.QuestionOptionDtoMapper; import org.waterforpeople.mapping.app.util.DtoMarshaller; import org.waterforpeople.mapping.app.web.dto.SurveyTaskRequest; import org.waterforpeople.mapping.app.web.rest.dto.QuestionPayload; import org.waterforpeople.mapping.app.web.rest.dto.RestStatusDto; import org.waterforpeople.mapping.dao.QuestionAnswerStoreDao; import javax.inject.Inject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Controller @RequestMapping("/questions") @SuppressWarnings("unused") public class QuestionRestService { @Inject private QuestionDao questionDao; @Inject private QuestionOptionDao questionOptionDao; @Inject private SurveyMetricMappingDao surveyMetricMappingDao; @Inject private SurveyDAO surveyDao; @Inject private SurveyGroupDAO surveyGroupDao; private QuestionDtoMapper questionDtoMapper = new QuestionDtoMapper(); private QuestionOptionDtoMapper questionOptionDtoMapper = new QuestionOptionDtoMapper(); // list questions by questionGroup or by survey. // if optionQuestionHeadersOnly is true, only the option questions are returned // and without any of the actual options loaded. In the dashboard, this is used // merely to make a choice between options, so not all details are necessary. @RequestMapping(method = RequestMethod.GET, value = "") @ResponseBody public Map<String, Object> listQuestions( @RequestParam(value = "questionGroupId", defaultValue = "") Long questionGroupId, @RequestParam(value = "surveyId", defaultValue = "") Long surveyId, @RequestParam(value = "optionQuestionsOnly", defaultValue = "") String optionQuestionsOnly, @RequestParam(value = "preflight", defaultValue = "") String preflight, @RequestParam(value = "questionId", defaultValue = "") Long questionId, @RequestParam(value = "cascadeResourceId", defaultValue = "") Long cascadeResourceId) { final Map<String, Object> response = new HashMap<>(); List<QuestionDto> results = new ArrayList<>(); List<QuestionOptionDto> qoResults = new ArrayList<>(); List<Question> questions = new ArrayList<>(); RestStatusDto statusDto = new RestStatusDto(); statusDto.setStatus(""); statusDto.setMessage(""); boolean optionQuestionOnly = "true".equals(optionQuestionsOnly); // if this is a pre-flight delete check, handle that if (preflight != null && preflight.equals("delete") && questionId != null) { QuestionAnswerStoreDao qasDao = new QuestionAnswerStoreDao(); SurveyalValueDao svDao = new SurveyalValueDao(); statusDto.setStatus("preflight-delete-question"); statusDto.setMessage("cannot_delete"); if (qasDao.listByQuestion(questionId).size() == 0 && svDao.listByQuestion(questionId).size() == 0) { statusDto.setMessage("can_delete"); statusDto.setKeyId(questionId); } // if questionGroupId is present, load questions in that group } else if (questionGroupId != null) { questions = questionDao.listQuestionsInOrderForGroup(questionGroupId); } else if (surveyId != null) { if (optionQuestionOnly) { questions = questionDao.listQuestionsInOrder(surveyId, Question.Type.OPTION); } else { questions = questionDao.listQuestionsInOrder(surveyId, null); } } else if (cascadeResourceId != null) { questions = questionDao.listByCascadeResourceId(cascadeResourceId); } if (questions != null && questions.size() > 0) { for (Question question : questions) { QuestionDto qDto = questionDtoMapper.transform(question); if (qDto != null) { if (question.getType() == Question.Type.OPTION && !optionQuestionOnly) { Map<Integer, QuestionOption> qoMap = questionOptionDao .listOptionByQuestion(qDto.getKeyId()); List<Long> qoList = new ArrayList<>(); for (QuestionOption qo : qoMap.values()) { QuestionOptionDto qoDto = questionOptionDtoMapper.transform(qo); qoList.add(qo.getKeyId()); qoResults.add(qoDto); } qDto.setQuestionOptions(qoList); } results.add(qDto); } } } response.put("questionOptions", qoResults); response.put("questions", results); response.put("meta", statusDto); return response; } // find a single question by the questionId @RequestMapping(method = RequestMethod.GET, value = "/{id}") @ResponseBody public Map<String, Object> findQuestion(@PathVariable("id") Long id) { final Map<String, Object> response = new HashMap<String, Object>(); List<QuestionOptionDto> qoResults = new ArrayList<QuestionOptionDto>(); Question q = questionDao.getByKey(id); QuestionDto dto = null; if (q != null) { dto = new QuestionDto(); DtoMarshaller.copyToDto(q, dto); if (q.getType() == Question.Type.OPTION) { Map<Integer, QuestionOption> qoMap = questionOptionDao.listOptionByQuestion(dto .getKeyId()); List<Long> qoList = new ArrayList<Long>(); for (QuestionOption qo : qoMap.values()) { QuestionOptionDto qoDto = new QuestionOptionDto(); BeanUtils.copyProperties(qo, qoDto, new String[] { "translationMap" }); qoDto.setKeyId(qo.getKey().getId()); qoList.add(qo.getKey().getId()); qoResults.add(qoDto); } dto.setQuestionOptions(qoList); } } response.put("questionOptions", qoResults); response.put("question", dto); return response; } // delete question by id @RequestMapping(method = RequestMethod.DELETE, value = "/{id}") @ResponseBody public Map<String, RestStatusDto> deleteQuestionById( @PathVariable("id") Long questionId) { final Map<String, RestStatusDto> response = new HashMap<String, RestStatusDto>(); Question q = questionDao.getByKey(questionId); RestStatusDto statusDto = null; statusDto = new RestStatusDto(); statusDto.setStatus("failed"); statusDto.setMessage("_cannot_delete"); // check if question exists in the datastore if (q != null) { try { TaskOptions deleteQuestionTask = TaskOptions.Builder .withUrl("/app_worker/surveytask") .param(SurveyTaskRequest.ACTION_PARAM, SurveyTaskRequest.DELETE_QUESTION_ACTION) .param(SurveyTaskRequest.ID_PARAM, questionId.toString()); QueueFactory.getQueue("deletequeue").add(deleteQuestionTask); statusDto.setStatus("ok"); statusDto.setMessage("deleted"); } catch (Exception e) { statusDto.setStatus("failed"); statusDto.setMessage(e.getMessage()); } } response.put("meta", statusDto); return response; } // update existing question // questionOptions are saved and updated on their own @RequestMapping(method = RequestMethod.PUT, value = "/{id}") @ResponseBody public Map<String, Object> saveExistingQuestion( @RequestBody QuestionPayload payLoad) { final QuestionDto questionDto = payLoad.getQuestion(); final Map<String, Object> response = new HashMap<String, Object>(); QuestionDto dto = null; RestStatusDto statusDto = new RestStatusDto(); statusDto.setStatus("failed"); statusDto.setMessage("Cannot change question"); // if the POST data contains a valid questionDto, continue. Otherwise, // server will respond with 400 Bad Request if (questionDto != null) { Long keyId = questionDto.getKeyId(); Question q; // if the questionDto has a key, try to get the question. if (keyId != null) { q = questionDao.getByKey(keyId); // if we find the question, update it's properties if (q != null) { // copy the properties, except the createdDateTime property, // because it is set in the Dao. BeanUtils.copyProperties(questionDto, q, new String[] { "createdDateTime", "type", "optionList" }); if (questionDto.getType() != null) q.setType(Question.Type.valueOf(questionDto.getType() .toString())); if (questionDto.getMetricId() != null) { // delete existing mappings surveyMetricMappingDao.deleteMetricMapping(keyId); // create a new mapping SurveyMetricMapping newMapping = new SurveyMetricMapping(); newMapping.setMetricId(questionDto.getMetricId()); newMapping.setQuestionGroupId(questionDto .getQuestionGroupId()); newMapping.setSurveyId(questionDto.getSurveyId()); newMapping.setSurveyQuestionId(keyId); surveyMetricMappingDao.save(newMapping); } q = questionDao.save(q); dto = new QuestionDto(); DtoMarshaller.copyToDto(q, dto); statusDto.setStatus("ok"); statusDto.setMessage(""); } } } response.put("meta", statusDto); response.put("question", dto); return response; } // create new question // questionOptions are saved and updated on their own @RequestMapping(method = RequestMethod.POST, value = "") @ResponseBody public Map<String, Object> saveNewQuestion( @RequestBody QuestionPayload payLoad) { final QuestionDto questionDto = payLoad.getQuestion(); final Map<String, Object> response = new HashMap<String, Object>(); List<QuestionOptionDto> qoResults = new ArrayList<QuestionOptionDto>(); QuestionDto dto = null; RestStatusDto statusDto = new RestStatusDto(); statusDto.setStatus("failed"); statusDto.setMessage("Cannot create question"); // if the POST data contains a valid questionDto, continue. Otherwise, // server will respond with 400 Bad Request if (questionDto != null) { Question q = null; if (questionDto.getSourceId() == null) { q = newQuestion(questionDto); } else { q = copyQuestion(questionDto); } dto = new QuestionDto(); DtoMarshaller.copyToDto(q, dto); statusDto.setStatus("ok"); statusDto.setMessage(""); if (q.getType() == Question.Type.OPTION) { Map<Integer, QuestionOption> qoMap = questionOptionDao.listOptionByQuestion(dto .getKeyId()); List<Long> qoList = new ArrayList<Long>(); for (QuestionOption qo : qoMap.values()) { QuestionOptionDto qoDto = new QuestionOptionDto(); BeanUtils.copyProperties(qo, qoDto, new String[] { "translationMap" }); qoDto.setKeyId(qo.getKey().getId()); qoList.add(qo.getKey().getId()); qoResults.add(qoDto); } dto.setQuestionOptions(qoList); } } response.put("meta", statusDto); response.put("questionOptions", qoResults); response.put("question", dto); return response; } @RequestMapping(method = RequestMethod.POST, value = "/{id}/validate") @ResponseBody public Map<String, Object> validateQuestionId( @PathVariable("id") Long id, @RequestParam(value = "questionId") String questionId) { Question question = questionDao.getByKey(id); Long surveyId = question.getSurveyId(); Survey survey = surveyDao.getById(surveyId); Long surveyGroupId = survey.getSurveyGroupId(); SurveyGroup surveyGroup = surveyGroupDao.getByKey(surveyGroupId); boolean isMonitoringGroup = surveyGroup.getMonitoringGroup(); List<Survey> surveys = new ArrayList<Survey>(); if (isMonitoringGroup) { surveys = surveyDao.listSurveysByGroup(surveyGroupId); } else { surveys.add(survey); } List<Question> questions = new ArrayList<Question>(); for (Survey s : surveys) { questions.addAll(questionDao.listQuestionsBySurvey(s.getKey().getId())); } Map<String, Object> result = new HashMap<String, Object>(); for (Question q : questions) { if (questionId.equals(q.getQuestionId()) && !question.getKey().equals(q.getKey())) { result.put("success", false); result.put("reason", "Question id not unique"); return result; } } result.put("success", true); return result; } private Question copyQuestion(QuestionDto dto) { final Question source = questionDao.getByKey(dto.getSourceId()); if (source == null) { // source question not found, the getByKey already logged the problem return null; } return SurveyUtils.copyQuestion(source, dto.getQuestionGroupId(), dto.getOrder(), source.getSurveyId(), SurveyUtils.listQuestionIdsUsedInSurveyGroup(source.getSurveyId())); } private Question newQuestion(QuestionDto dto) { final Question q = new Question(); // copy the properties, except the createdDateTime property, because // it is set in the Dao. BeanUtils.copyProperties(dto, q, new String[] { "createdDateTime", "type" }); if (dto.getType() != null) { q.setType(Question.Type.valueOf(dto.getType() .toString())); } final Question result = questionDao.save(q); return result; } }