/*
* Copyright (C) 2010-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.gwt.server.survey;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.jsr107cache.Cache;
import net.sf.jsr107cache.CacheException;
import net.sf.jsr107cache.CacheFactory;
import net.sf.jsr107cache.CacheManager;
import org.waterforpeople.mapping.app.gwt.client.survey.OptionContainerDto;
import org.waterforpeople.mapping.app.gwt.client.survey.QuestionDependencyDto;
import org.waterforpeople.mapping.app.gwt.client.survey.QuestionDto;
import org.waterforpeople.mapping.app.gwt.client.survey.QuestionDto.QuestionType;
import org.waterforpeople.mapping.app.gwt.client.survey.QuestionGroupDto;
import org.waterforpeople.mapping.app.gwt.client.survey.QuestionHelpDto;
import org.waterforpeople.mapping.app.gwt.client.survey.QuestionOptionDto;
import org.waterforpeople.mapping.app.gwt.client.survey.SurveyDto;
import org.waterforpeople.mapping.app.gwt.client.survey.SurveyGroupDto;
import org.waterforpeople.mapping.app.gwt.client.survey.SurveyService;
import org.waterforpeople.mapping.app.gwt.client.survey.TranslationDto;
import org.waterforpeople.mapping.app.util.DtoMarshaller;
import org.waterforpeople.mapping.app.web.DataProcessorRestServlet;
import org.waterforpeople.mapping.app.web.dto.BootstrapGeneratorRequest;
import org.waterforpeople.mapping.app.web.dto.SurveyAssemblyRequest;
import org.waterforpeople.mapping.app.web.dto.SurveyTaskRequest;
import org.waterforpeople.mapping.dao.SurveyContainerDao;
import org.waterforpeople.mapping.dao.SurveyInstanceDAO;
import com.gallatinsystems.common.Constants;
import com.gallatinsystems.common.util.PropertyUtil;
import com.gallatinsystems.framework.exceptions.IllegalDeletionException;
import com.gallatinsystems.messaging.dao.MessageDao;
import com.gallatinsystems.messaging.domain.Message;
import com.gallatinsystems.metric.dao.SurveyMetricMappingDao;
import com.gallatinsystems.metric.domain.SurveyMetricMapping;
import com.gallatinsystems.survey.dao.CascadeResourceDao;
import com.gallatinsystems.survey.dao.QuestionDao;
import com.gallatinsystems.survey.dao.QuestionGroupDao;
import com.gallatinsystems.survey.dao.QuestionHelpMediaDao;
import com.gallatinsystems.survey.dao.SurveyDAO;
import com.gallatinsystems.survey.dao.SurveyGroupDAO;
import com.gallatinsystems.survey.dao.TranslationDao;
import com.gallatinsystems.survey.domain.CascadeResource;
import com.gallatinsystems.survey.domain.Question;
import com.gallatinsystems.survey.domain.QuestionGroup;
import com.gallatinsystems.survey.domain.QuestionHelpMedia;
import com.gallatinsystems.survey.domain.QuestionOption;
import com.gallatinsystems.survey.domain.Survey;
import com.gallatinsystems.survey.domain.SurveyContainer;
import com.gallatinsystems.survey.domain.SurveyGroup;
import com.gallatinsystems.survey.domain.Translation;
import com.gallatinsystems.survey.domain.Translation.ParentType;
import com.gallatinsystems.survey.domain.xml.AltText;
import com.gallatinsystems.survey.domain.xml.Dependency;
import com.gallatinsystems.survey.domain.xml.Heading;
import com.gallatinsystems.survey.domain.xml.Help;
import com.gallatinsystems.survey.domain.xml.ObjectFactory;
import com.gallatinsystems.survey.domain.xml.Option;
import com.gallatinsystems.survey.domain.xml.Options;
import com.gallatinsystems.survey.domain.xml.Text;
import com.gallatinsystems.survey.domain.xml.ValidationRule;
import com.gallatinsystems.survey.xml.SurveyXMLAdapter;
import com.gallatinsystems.surveyal.app.web.SurveyalRestRequest;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.jsr107cache.GCacheFactory;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class SurveyServiceImpl extends RemoteServiceServlet implements
SurveyService {
public static final String FREE_QUESTION_TYPE = "free";
public static final String OPTION_QUESTION_TYPE = "option";
public static final String GEO_QUESTION_TYPE = "geo";
public static final String VIDEO_QUESTION_TYPE = "video";
public static final String PHOTO_QUESTION_TYPE = "photo";
public static final String SCAN_QUESTION_TYPE = "scan";
public static final String STRENGTH_QUESTION_TYPE = "strength";
private static final String SURVEY_S3_PROP = "surveyuploadurl";
private static final String SURVEY_DIR_PROP = "surveyuploaddir";
private static final String PUB_CACHE_PREFIX = "pubsrv";
private static final String SURVEY_UPDATE_MESSAGE_ACTION = "surveyUpdate";
private static final String SURVEY_CHANGE_COMPLTE_MESSAGE_ACTION = "surveyChangeComplete";
private static final String SURVEY_UPDATE_MESSAGE = "Survey has been updated. Please publish it to release it to devices.";
private static final String SURVEY_CHANGE_COMPLETE_MESSAGE = "Survey changes have been marked as complete. Please publish it to release it to devices.";
private static final int CACHE_EXPIRY_DEFAULT = 3600;
private static final String CACHE_EXP_PROP = "cacheExpirySeconds";
private static final Logger log = Logger.getLogger(SurveyServiceImpl.class
.getName());
private static final long serialVersionUID = 5557965649047558451L;
private SurveyDAO surveyDao;
private Cache cache;
private MessageDao messageDao;
private int cacheExpirySec = CACHE_EXPIRY_DEFAULT;
@SuppressWarnings({
"rawtypes", "unchecked"
})
public SurveyServiceImpl() {
surveyDao = new SurveyDAO();
messageDao = new MessageDao();
try {
CacheFactory cacheFactory = CacheManager.getInstance()
.getCacheFactory();
Map configMap = new HashMap();
String cacheExpString = PropertyUtil.getProperty(CACHE_EXP_PROP);
if (cacheExpString != null) {
try {
cacheExpirySec = Integer.parseInt(cacheExpString);
} catch (Exception e) {
// no-op
}
}
if (cacheExpirySec <= 0) {
cacheExpirySec = CACHE_EXPIRY_DEFAULT;
}
configMap.put(GCacheFactory.EXPIRATION_DELTA, cacheExpirySec);
configMap.put(MemcacheService.SetPolicy.SET_ALWAYS, true);
cache = cacheFactory.createCache(configMap);
} catch (CacheException e) {
log.log(Level.SEVERE, "Could not initialize cache", e);
}
}
@Override
public SurveyDto[] listSurvey() {
List<Survey> surveys = surveyDao.list(Constants.ALL_RESULTS);
SurveyDto[] surveyDtos = null;
if (surveys != null) {
surveyDtos = new SurveyDto[surveys.size()];
for (int i = 0; i < surveys.size(); i++) {
SurveyDto dto = new SurveyDto();
Survey s = surveys.get(i);
dto.setName(s.getName());
dto.setVersion(s.getVersion() != null ? s.getVersion()
.toString() : "");
dto.setKeyId(s.getKey().getId());
surveyDtos[i] = dto;
}
}
return surveyDtos;
}
/**
* This method will return a list of all the questions that have a specific type code
*/
@Override
public QuestionDto[] listSurveyQuestionByType(Long surveyId,
QuestionType type, boolean loadTranslations) {
QuestionDao questionDao = new QuestionDao();
List<Question> qList = questionDao.listQuestionsInOrder(surveyId,
Question.Type.valueOf(type.toString()));
QuestionDto[] dtoArr = new QuestionDto[qList.size()];
int i = 0;
TranslationDao transDao = new TranslationDao();
for (Question q : qList) {
if (loadTranslations) {
q.setTranslationMap(transDao.findTranslations(
ParentType.QUESTION_TEXT, q.getKey().getId()));
}
dtoArr[i] = marshalQuestionDto(q);
i++;
}
return dtoArr;
}
/**
* lists all surveys for a group
*/
@Override
public ArrayList<SurveyDto> listSurveysByGroup(String surveyGroupId) {
SurveyDAO dao = new SurveyDAO();
List<Survey> surveys = dao.listSurveysByGroup(Long
.parseLong(surveyGroupId));
ArrayList<SurveyDto> surveyDtos = null;
if (surveys != null) {
surveyDtos = new ArrayList<SurveyDto>();
for (Survey s : surveys) {
SurveyDto dto = new SurveyDto();
dto.setName(s.getName());
dto.setVersion(s.getVersion() != null ? s.getVersion()
.toString() : "");
dto.setKeyId(s.getKey().getId());
dto.setPath(s.getPath());
dto.setCode(s.getCode());
dto.setPointType(s.getPointType());
dto.setDefaultLanguageCode(s.getDefaultLanguageCode());
if (s.getStatus() != null) {
dto.setStatus(s.getStatus().toString());
}
dto.setRequireApproval(s.getRequireApproval());
surveyDtos.add(dto);
}
}
return surveyDtos;
}
public static QuestionDto marshalQuestionDto(Question q) {
QuestionDto qDto = new QuestionDto();
DtoMarshaller.copyToDto(q, qDto);
if (q.getQuestionHelpMediaMap() != null) {
for (QuestionHelpMedia help : q.getQuestionHelpMediaMap().values()) {
QuestionHelpDto dto = new QuestionHelpDto();
Map<String, Translation> transMap = help.getTranslationMap();
help.setTranslationMap(null);
DtoMarshaller.copyToDto(help, dto);
if (transMap != null) {
dto.setTranslationMap(marshalTranslations(transMap));
}
qDto.addQuestionHelp(dto);
}
}
if (q.getQuestionOptionMap() != null) {
OptionContainerDto ocDto = new OptionContainerDto();
if (q.getAllowOtherFlag() != null)
ocDto.setAllowOtherFlag(q.getAllowOtherFlag());
if (q.getAllowMultipleFlag() != null)
ocDto.setAllowMultipleFlag(q.getAllowMultipleFlag());
for (QuestionOption qo : q.getQuestionOptionMap().values()) {
QuestionOptionDto ooDto = new QuestionOptionDto();
ooDto.setTranslationMap(marshalTranslations(qo
.getTranslationMap()));
ooDto.setKeyId(qo.getKey().getId());
if (qo.getCode() != null)
ooDto.setCode(qo.getCode());
if (qo.getText() != null)
ooDto.setText(qo.getText());
ooDto.setOrder(qo.getOrder());
ocDto.addQuestionOption(ooDto);
}
qDto.setOptionContainerDto(ocDto);
}
if (q.getDependentQuestionId() != null) {
QuestionDependencyDto qdDto = new QuestionDependencyDto();
qdDto.setQuestionId(q.getDependentQuestionId());
qdDto.setAnswerValue(q.getDependentQuestionAnswer());
qDto.setQuestionDependency(qdDto);
}
qDto.setTranslationMap(marshalTranslations(q.getTranslationMap()));
if (Question.Type.CASCADE.equals(q.getType()) && q.getCascadeResourceId() != null) {
qDto.setLevelNames(getCascadeResourceLevelNames(q.getCascadeResourceId()));
}
return qDto;
}
private static List<String> getCascadeResourceLevelNames(Long id) {
final CascadeResource cr = new CascadeResourceDao().getByKey(id);
if (cr == null || cr.getLevelNames() == null || cr.getLevelNames().isEmpty()) {
return null;
}
return cr.getLevelNames();
}
private static TreeMap<String, TranslationDto> marshalTranslations(
Map<String, Translation> translationMap) {
TreeMap<String, TranslationDto> transMap = null;
if (translationMap != null && translationMap.size() > 0) {
transMap = new TreeMap<String, TranslationDto>();
for (Translation trans : translationMap.values()) {
TranslationDto tDto = new TranslationDto();
tDto.setKeyId(trans.getKey().getId());
tDto.setLangCode(trans.getLanguageCode());
tDto.setText(trans.getText());
tDto.setParentId(trans.getParentId());
tDto.setParentType(trans.getParentType().toString());
transMap.put(tDto.getLangCode(), tDto);
}
}
return transMap;
}
private static TreeMap<String, Translation> marshalFromDtoTranslations(
Map<String, TranslationDto> translationMap) {
TreeMap<String, Translation> transMap = null;
if (translationMap != null && translationMap.size() > 0) {
transMap = new TreeMap<String, Translation>();
for (TranslationDto trans : translationMap.values()) {
Translation t = new Translation();
if (trans.getKeyId() != null)
t.setKey((KeyFactory.createKey(
Translation.class.getSimpleName(), trans.getKeyId())));
t.setLanguageCode(trans.getLangCode());
t.setText(trans.getText());
t.setParentId(trans.getParentId());
if (trans.getParentType().equals(
Translation.ParentType.QUESTION_TEXT.toString())) {
t.setParentType(ParentType.QUESTION_TEXT);
} else if (trans.getParentType().equals(
Translation.ParentType.QUESTION_OPTION.toString())) {
t.setParentType(ParentType.QUESTION_OPTION);
} else if (Translation.ParentType.QUESTION_HELP_MEDIA_TEXT
.toString().equals(trans.getParentType())) {
t.setParentType(ParentType.QUESTION_HELP_MEDIA_TEXT);
}
transMap.put(t.getLanguageCode(), t);
}
}
return transMap;
}
public Question marshalQuestion(QuestionDto qdto) {
Question q = new Question();
DtoMarshaller.copyToCanonical(q, qdto);
/*
* TODO: remove as same code seems to be duplicated later in this method if
* (qdto.getQuestionHelpList() != null) { List<QuestionHelpDto> qHListDto =
* qdto.getQuestionHelpList(); for (QuestionHelpDto qhDto : qHListDto) { QuestionHelpMedia
* qh = new QuestionHelpMedia(); DtoMarshaller.copyToCanonical(qh, qhDto); } }
*/
if (qdto.getOptionContainerDto() != null) {
OptionContainerDto ocDto = qdto.getOptionContainerDto();
if (ocDto.getAllowOtherFlag() != null) {
q.setAllowOtherFlag(ocDto.getAllowOtherFlag());
}
if (ocDto.getAllowMultipleFlag() != null) {
q.setAllowMultipleFlag(ocDto.getAllowMultipleFlag());
}
if (ocDto.getOptionsList() != null) {
ArrayList<QuestionOptionDto> optionDtoList = ocDto
.getOptionsList();
for (QuestionOptionDto qoDto : optionDtoList) {
QuestionOption oo = new QuestionOption();
if (qoDto.getKeyId() != null)
oo.setKey((KeyFactory.createKey(
QuestionOption.class.getSimpleName(),
qoDto.getKeyId())));
if (qoDto.getCode() != null)
oo.setCode(qoDto.getCode());
if (qoDto.getText() != null)
oo.setText(qoDto.getText());
oo.setOrder(qoDto.getOrder());
// Hack
if (qoDto.getTranslationMap() != null) {
TreeMap<String, Translation> transTreeMap = SurveyServiceImpl
.marshalFromDtoTranslations(qoDto
.getTranslationMap());
HashMap<String, Translation> transMap = new HashMap<String, Translation>();
for (Map.Entry<String, Translation> entry : transTreeMap
.entrySet()) {
transMap.put(entry.getKey(), entry.getValue());
}
oo.setTranslationMap(transMap);
}
q.addQuestionOption(oo);
}
}
}
/*
* TODO: remove. probably not necessary as already covered by dependentFlag,
* dependentQuestionId, and dependentQUestionAnswer members in schema if
* (qdto.getQuestionDependency() != null) {
* q.setDependentQuestionId(qdto.getQuestionDependency() .getQuestionId());
* q.setDependentQuestionAnswer(qdto.getQuestionDependency() .getAnswerValue());
* q.setDependentFlag(true); }
*/
if (qdto.getTranslationMap() != null) {
TreeMap<String, Translation> transMap = marshalFromDtoTranslations(qdto
.getTranslationMap());
q.setTranslationMap(transMap);
}
if (qdto.getQuestionHelpList() != null) {
int count = 0;
for (QuestionHelpDto help : qdto.getQuestionHelpList()) {
QuestionHelpMedia helpDomain = new QuestionHelpMedia();
Map<String, TranslationDto> transMap = help.getTranslationMap();
help.setTranslationMap(null);
DtoMarshaller.copyToCanonical(helpDomain, help);
if (transMap != null) {
helpDomain
.setTranslationMap(marshalFromDtoTranslations(transMap));
}
q.addHelpMedia(count++, helpDomain);
}
}
return q;
}
/**
* fully hydrates a single survey object
*/
@Override
public SurveyDto loadFullSurvey(Long surveyId) {
Survey survey = surveyDao.loadFullSurvey(surveyId);
SurveyDto dto = null;
if (survey != null) {
dto = new SurveyDto();
DtoMarshaller.copyToDto(survey, dto);
dto.setQuestionGroupList(null);
if (survey.getQuestionGroupMap() != null) {
ArrayList<QuestionGroupDto> qGroupDtoList = new ArrayList<QuestionGroupDto>();
for (QuestionGroup qg : survey.getQuestionGroupMap().values()) {
QuestionGroupDto qgDto = new QuestionGroupDto();
DtoMarshaller.copyToDto(qg, qgDto);
qgDto.setQuestionMap(null);
qGroupDtoList.add(qgDto);
if (qg.getQuestionMap() != null) {
TreeMap<Integer, QuestionDto> qDtoMap = new TreeMap<Integer, QuestionDto>();
for (Entry<Integer, Question> entry : qg
.getQuestionMap().entrySet()) {
QuestionDto qdto = marshalQuestionDto(entry
.getValue());
qDtoMap.put(entry.getKey(), qdto);
}
qgDto.setQuestionMap(qDtoMap);
}
}
dto.setQuestionGroupList(qGroupDtoList);
}
}
return dto;
}
@Override
public List<SurveyDto> listSurveysForSurveyGroup(String surveyGroupId) {
List<Survey> surveyList = surveyDao.listSurveysByGroup(Long
.parseLong(surveyGroupId));
List<SurveyDto> surveyDtoList = new ArrayList<SurveyDto>();
for (Survey canonical : surveyList) {
SurveyDto dto = new SurveyDto();
DtoMarshaller.copyToDto(canonical, dto);
surveyDtoList.add(dto);
}
return surveyDtoList;
}
@Override
public ArrayList<QuestionGroupDto> listQuestionGroupsBySurvey(
String surveyId) {
QuestionGroupDao questionGroupDao = new QuestionGroupDao();
TreeMap<Integer, QuestionGroup> questionGroupList = questionGroupDao
.listQuestionGroupsBySurvey(new Long(surveyId));
ArrayList<QuestionGroupDto> questionGroupDtoList = new ArrayList<QuestionGroupDto>();
for (QuestionGroup canonical : questionGroupList.values()) {
QuestionGroupDto dto = new QuestionGroupDto();
DtoMarshaller.copyToDto(canonical, dto);
questionGroupDtoList.add(dto);
}
return questionGroupDtoList;
}
@Override
public ArrayList<QuestionDto> listQuestionsByQuestionGroup(
String questionGroupId, boolean needDetails) {
return listQuestionsByQuestionGroup(questionGroupId, needDetails, true);
}
@Override
public ArrayList<QuestionDto> listQuestionsByQuestionGroup(
String questionGroupId, boolean needDetails,
boolean allowSideEffects) {
QuestionDao questionDao = new QuestionDao();
java.util.ArrayList<QuestionDto> questionDtoList = new ArrayList<QuestionDto>();
if (allowSideEffects) {
TreeMap<Integer, Question> questionList = questionDao
.listQuestionsByQuestionGroup(
Long.parseLong(questionGroupId), needDetails,
allowSideEffects);
if (questionList != null && !questionList.isEmpty()) {
for (Question canonical : questionList.values()) {
QuestionDto dto = marshalQuestionDto(canonical);
questionDtoList.add(dto);
}
}
} else {
List<Question> questionList = questionDao
.listQuestionsInOrderForGroup(new Long(questionGroupId));
if (questionList != null && !questionList.isEmpty()) {
for (Question canonical : questionList) {
QuestionDto dto = marshalQuestionDto(canonical);
questionDtoList.add(dto);
}
}
}
if (questionDtoList.size() > 0) {
return questionDtoList;
} else {
return null;
}
}
@Override
public String deleteQuestion(QuestionDto value, Long questionGroupId) {
QuestionDao questionDao = new QuestionDao();
Question canonical = new Question();
DtoMarshaller.copyToCanonical(canonical, value);
try {
questionDao.delete(canonical);
} catch (IllegalDeletionException e) {
return e.getError();
}
return null;
}
@Override
public QuestionDto saveQuestion(QuestionDto value, Long questionGroupId,
boolean forceReorder) {
QuestionDao questionDao = new QuestionDao();
Question question = marshalQuestion(value);
if (forceReorder) {
TreeMap<Integer, Question> questions = questionDao
.listQuestionsByQuestionGroup(questionGroupId, false);
if (questions != null) {
for (Question q : questions.values()) {
if (q.getOrder() >= value.getOrder()) {
q.setOrder(q.getOrder() + 1);
}
}
}
}
question = questionDao.save(question, questionGroupId);
saveSurveyUpdateMessage(question.getSurveyId());
return marshalQuestionDto(question);
}
@Override
public QuestionGroupDto saveQuestionGroup(QuestionGroupDto dto,
Long surveyId) {
QuestionGroup questionGroup = new QuestionGroup();
DtoMarshaller.copyToCanonical(questionGroup, dto);
QuestionGroupDao questionGroupDao = new QuestionGroupDao();
if (questionGroup.getOrder() == null || questionGroup.getOrder() == 0) {
Map<Integer, QuestionGroup> items = questionGroupDao
.listQuestionGroupsBySurvey(questionGroup.getSurveyId());
if (items != null) {
questionGroup.setOrder(items.size() + 1);
} else {
questionGroup.setOrder(1);
}
}
questionGroup = questionGroupDao.save(questionGroup, surveyId);
saveSurveyUpdateMessage(questionGroup.getSurveyId());
DtoMarshaller.copyToDto(questionGroup, dto);
return dto;
}
@Override
public List<QuestionGroupDto> saveQuestionGroups(
List<QuestionGroupDto> dtoList) {
QuestionGroupDao questionGroupDao = new QuestionGroupDao();
Set<Long> surveyIds = new HashSet<Long>();
if (dtoList != null) {
List<QuestionGroup> groupList = new ArrayList<QuestionGroup>();
int i = 0;
for (QuestionGroupDto dto : dtoList) {
QuestionGroup questionGroup = new QuestionGroup();
DtoMarshaller.copyToCanonical(questionGroup, dto);
if (questionGroup.getOrder() == null
|| questionGroup.getOrder() == 0) {
questionGroup.setOrder(i);
}
groupList.add(questionGroup);
surveyIds.add(questionGroup.getSurveyId());
i++;
}
questionGroupDao.save(groupList);
for (int j = 0; j < groupList.size(); j++) {
dtoList.get(j).setKeyId(groupList.get(j).getKey().getId());
}
for (Long surveyId : surveyIds) {
saveSurveyUpdateMessage(surveyId);
}
}
return dtoList;
}
@Override
public SurveyDto saveSurvey(SurveyDto surveyDto, Long surveyGroupId) {
Survey canonical = new Survey();
DtoMarshaller.copyToCanonical(canonical, surveyDto);
canonical.setStatus(Survey.Status.NOT_PUBLISHED);
SurveyDAO surveyDao = new SurveyDAO();
if (canonical.getKey() != null && canonical.getSurveyGroupId() == 0) {
// fetch record from db so we don't loose assoc
Survey sTemp = surveyDao.getByKey(canonical.getKey());
canonical.setSurveyGroupId(sTemp.getSurveyGroupId());
canonical.setPath(sTemp.getPath());
}
canonical = surveyDao.save(canonical);
DtoMarshaller.copyToDto(canonical, surveyDto);
saveSurveyUpdateMessage(canonical.getKey().getId());
return surveyDto;
}
@Override
public SurveyGroupDto saveSurveyGroup(SurveyGroupDto dto) {
SurveyGroup canonical = new SurveyGroup();
SurveyGroupDAO surveyGroupDao = new SurveyGroupDAO();
DtoMarshaller.copyToCanonical(canonical, dto);
canonical = surveyGroupDao.save(canonical);
DtoMarshaller.copyToDto(canonical, dto);
return dto;
}
/**
* saves or updates a list of translation objects and returns the saved value. If the text is
* null or blank and the ID is populated, that translation will be deleted since we shouldn't
* allow blank translations.
*/
@Override
public List<TranslationDto> saveTranslations(
List<TranslationDto> translations) {
TranslationDao translationDao = new TranslationDao();
List<TranslationDto> deletedItems = new ArrayList<TranslationDto>();
Set<Long> questionIdSet = new HashSet<Long>();
Set<Long> questionOptionIdSet = new HashSet<Long>();
for (TranslationDto t : translations) {
Translation transDomain = new Translation();
// need to work around marshaller's inability to translate string to
// enumeration values. We need to set it back after the copy call
String parentType = t.getParentType();
t.setParentType(null);
DtoMarshaller.copyToCanonical(transDomain, t);
t.setParentType(parentType);
transDomain.setParentType(ParentType.valueOf(parentType));
if (ParentType.QUESTION_TEXT == transDomain.getParentType()) {
questionIdSet.add(t.getParentId());
} else if (ParentType.QUESTION_OPTION == transDomain
.getParentType()) {
questionOptionIdSet.add(t.getParentId());
}
transDomain.setLanguageCode(t.getLangCode());
if (transDomain.getKey() != null
&& (transDomain.getText() == null || transDomain.getText()
.trim().length() == 0)) {
Translation itemToDelete = translationDao.getByKey(transDomain
.getKey());
if (itemToDelete != null) {
translationDao.delete(itemToDelete);
deletedItems.add(t);
}
} else {
transDomain = translationDao.save(transDomain);
}
t.setKeyId(transDomain.getKey().getId());
}
translations.removeAll(deletedItems);
QuestionDao questionDao = new QuestionDao();
for (Long optId : questionOptionIdSet) {
QuestionOption opt = questionDao.getByKey(optId,
QuestionOption.class);
if (opt != null) {
questionIdSet.add(opt.getQuestionId());
}
}
for (Long questionId : questionIdSet) {
Question question = questionDao.getByKey(questionId);
if (question != null) {
saveSurveyUpdateMessage(question.getSurveyId());
}
}
return translations;
}
@Override
public void publishSurveyAsync(Long surveyId) {
TaskOptions options = TaskOptions.Builder
.withUrl("/app_worker/surveyassembly")
.param("action", SurveyAssemblyRequest.ASSEMBLE_SURVEY)
.param("surveyId", surveyId.toString());
com.google.appengine.api.taskqueue.Queue queue = com.google.appengine.api.taskqueue.QueueFactory
.getQueue("surveyAssembly");
queue.add(options);
Survey s = new SurveyDAO().getById(surveyId);
SurveyGroup sg = s != null ? new SurveyGroupDAO().getByKey(s.getSurveyGroupId()) : null;
if (sg != null && sg.getNewLocaleSurveyId() != null &&
sg.getNewLocaleSurveyId().longValue() == surveyId.longValue()) {
// This is the registration form. Schedule datapoint name re-assembly
DataProcessorRestServlet.scheduleDatapointNameAssembly(sg.getKey().getId(), null);
}
}
@Override
public QuestionDto loadQuestionDetails(Long questionId) {
QuestionDao questionDao = new QuestionDao();
Question canonical = questionDao.getByKey(questionId, true);
if (canonical != null) {
return marshalQuestionDto(canonical);
} else {
return null;
}
}
@Override
public String deleteQuestionGroup(QuestionGroupDto value, Long surveyId) {
if (value != null) {
QuestionGroupDao qgDao = new QuestionGroupDao();
try {
qgDao.delete(qgDao.getByKey(value.getKeyId()));
} catch (IllegalDeletionException e) {
// ignore
}
}
return null;
}
@Override
public String deleteSurvey(SurveyDto value, Long surveyGroupId) {
if (value != null) {
SurveyDAO surveyDao = new SurveyDAO();
try {
Survey s = new Survey();
DtoMarshaller.copyToCanonical(s, value);
surveyDao.delete(s);
} catch (IllegalDeletionException e) {
log.log(Level.SEVERE, "Could not delete survey", e);
}
}
return null;
}
@Override
public String deleteSurveyGroup(SurveyGroupDto value) {
if (value != null) {
SurveyGroupDAO surveyGroupDao = new SurveyGroupDAO();
surveyGroupDao.delete(marshallSurveyGroup(value));
}
return null;
}
public static SurveyGroup marshallSurveyGroup(SurveyGroupDto dto) {
SurveyGroup sg = new SurveyGroup();
if (dto.getKeyId() != null)
sg.setKey(KeyFactory.createKey(SurveyGroup.class.getSimpleName(),
dto.getKeyId()));
if (dto.getCode() != null)
sg.setCode(dto.getCode());
return sg;
}
@Override
public void rerunAPMappings(Long surveyId) {
Queue queue = QueueFactory.getDefaultQueue();
if (PropertyUtil.getProperty("domainType") != null
&& PropertyUtil.getProperty("domainType").equalsIgnoreCase(
"locale")) {
log.log(Level.INFO, "Running Remap for locale");
queue.add(TaskOptions.Builder
.withUrl("/app_worker/surveyalservlet")
.param(SurveyalRestRequest.ACTION_PARAM,
SurveyalRestRequest.RERUN_ACTION)
.param(SurveyalRestRequest.SURVEY_ID_PARAM,
surveyId.toString()));
} else {
log.log(Level.INFO, "AccessPoint");
SurveyInstanceDAO siDao = new SurveyInstanceDAO();
Iterable<Entity> siList = siDao.listSurveyInstanceKeysBySurveyId(surveyId);
if (siList != null) {
StringBuffer buffer = new StringBuffer();
int i = 0;
for (Entity item : siList) {
if (i > 0) {
buffer.append(",");
}
String key = item.getKey().toString();
Integer startPos = key.indexOf("(");
Integer endPos = key.indexOf(")");
buffer.append(key.subSequence(startPos + 1, endPos));
i++;
}
queue.add(TaskOptions.Builder
.withUrl("/app_worker/surveytask")
.param("action", "reprocessMapSurveyInstance")
.param(SurveyTaskRequest.ID_PARAM, surveyId.toString())
.param(SurveyTaskRequest.ID_LIST_PARAM,
buffer.toString())
// .param(SurveyTaskRequest.CURSOR_PARAM,
// SurveyInstanceDAO.getCursor(siList))
);
}
}
}
@Override
public List<QuestionHelpDto> listHelpByQuestion(Long questionId) {
QuestionHelpMediaDao helpDao = new QuestionHelpMediaDao();
TreeMap<Integer, QuestionHelpMedia> helpMedia = helpDao
.listHelpByQuestion(questionId);
List<QuestionHelpDto> dtoList = new ArrayList<QuestionHelpDto>();
if (helpMedia != null) {
for (QuestionHelpMedia help : helpMedia.values()) {
QuestionHelpDto dto = new QuestionHelpDto();
Map<String, Translation> transMap = help.getTranslationMap();
help.setTranslationMap(null);
DtoMarshaller.copyToDto(help, dto);
if (transMap != null) {
dto.setTranslationMap(marshalTranslations(transMap));
}
dtoList.add(dto);
}
}
return dtoList;
}
@Override
public List<QuestionHelpDto> saveHelp(List<QuestionHelpDto> helpList) {
QuestionHelpMediaDao helpDao = new QuestionHelpMediaDao();
Set<Long> questionIdSet = new HashSet<Long>();
if (helpList != null && helpList.size() > 0) {
Collection<QuestionHelpMedia> domainList = new ArrayList<QuestionHelpMedia>();
for (QuestionHelpDto dto : helpList) {
QuestionHelpMedia canonical = new QuestionHelpMedia();
DtoMarshaller.copyToCanonical(canonical, dto);
domainList.add(canonical);
questionIdSet.add(dto.getQuestionId());
}
domainList = helpDao.save(domainList);
helpList.clear();
for (QuestionHelpMedia domain : domainList) {
QuestionHelpDto dto = new QuestionHelpDto();
DtoMarshaller.copyToDto(domain, dto);
helpList.add(dto);
}
}
QuestionDao questionDao = new QuestionDao();
for (Long questionId : questionIdSet) {
Question question = questionDao.getByKey(questionId);
if (question != null) {
saveSurveyUpdateMessage(question.getSurveyId());
}
}
return helpList;
}
@Override
public Map<String, TranslationDto> listTranslations(Long parentId,
String parentType) {
TranslationDao transDao = new TranslationDao();
Map<String, Translation> transMap = transDao.findTranslations(
Translation.ParentType.valueOf(parentType), parentId);
return marshalTranslations(transMap);
}
@Override
public QuestionDto copyQuestion(QuestionDto existingQuestion,
QuestionGroupDto newParentGroup) {
Question questionToSave = marshalQuestion(existingQuestion);
// now override all the IDs
questionToSave.setKey(null);
questionToSave.setPath(newParentGroup.getPath() + "/"
+ newParentGroup.getName());
questionToSave.setQuestionGroupId(newParentGroup.getKeyId());
if (questionToSave.getQuestionOptionMap() != null) {
for (QuestionOption opt : questionToSave.getQuestionOptionMap()
.values()) {
opt.setKey(null);
opt.setQuestionId(null);
if (opt.getTranslationMap() != null) {
for (Translation t : opt.getTranslationMap().values()) {
t.setKey(null);
t.setParentId(null);
}
}
}
}
if (questionToSave.getTranslationMap() != null) {
for (Translation t : questionToSave.getTranslationMap().values()) {
t.setParentId(null);
t.setKey(null);
}
}
if (questionToSave.getQuestionHelpMediaMap() != null) {
for (QuestionHelpMedia help : questionToSave
.getQuestionHelpMediaMap().values()) {
help.setKey(null);
if (help.getTranslationMap() != null) {
for (Translation t : help.getTranslationMap().values()) {
t.setParentId(null);
t.setKey(null);
}
}
}
}
QuestionDao dao = new QuestionDao();
questionToSave = dao.save(questionToSave, newParentGroup.getKeyId());
// now see if we have a metric mapping to copy
SurveyMetricMappingDao mappingDao = new SurveyMetricMappingDao();
List<SurveyMetricMapping> mappings = mappingDao
.listMappingsByQuestion(existingQuestion.getKeyId());
if (mappings != null) {
List<SurveyMetricMapping> newMappings = new ArrayList<SurveyMetricMapping>();
for (SurveyMetricMapping mapping : mappings) {
SurveyMetricMapping newMapping = new SurveyMetricMapping();
newMapping.setQuestionGroupId(questionToSave
.getQuestionGroupId());
newMapping.setMetricId(mapping.getMetricId());
newMapping.setSurveyId(questionToSave.getSurveyId());
newMapping.setSurveyQuestionId(questionToSave.getKey().getId());
newMappings.add(newMapping);
}
if (newMappings.size() > 0) {
mappingDao.save(newMappings);
}
}
return marshalQuestionDto(questionToSave);
}
/**
* updates the order for the list of questions passed in
*
* @param q1
* @param q2
* @return
*/
@Override
public void updateQuestionOrder(List<QuestionDto> questions) {
if (questions != null) {
List<Question> questionList = new ArrayList<Question>();
for (QuestionDto qDto : questions) {
Question q = new Question();
q.setKey(KeyFactory.createKey(Question.class.getSimpleName(),
qDto.getKeyId()));
q.setOrder(qDto.getOrder());
questionList.add(q);
}
QuestionDao dao = new QuestionDao();
dao.updateQuestionOrder(questionList);
}
}
/**
* updates the order for the list of question groups passed in
*
* @param q1
* @param q2
* @return
*/
@Override
public void updateQuestionGroupOrder(List<QuestionGroupDto> groups) {
if (groups != null) {
List<QuestionGroup> groupList = new ArrayList<QuestionGroup>();
for (QuestionGroupDto qDto : groups) {
QuestionGroup q = new QuestionGroup();
q.setKey(KeyFactory.createKey(
QuestionGroup.class.getSimpleName(), qDto.getKeyId()));
q.setOrder(qDto.getOrder());
groupList.add(q);
}
QuestionDao dao = new QuestionDao();
dao.updateQuestionGroupOrder(groupList);
}
}
/**
* updates a question with new dependency information.
*
* @param questionId
* @param dep
*/
@Override
public void updateQuestionDependency(Long questionId,
QuestionDependencyDto dep) {
QuestionDao qDao = new QuestionDao();
Question q = qDao.getByKey(questionId, false);
if (q != null) {
if (dep != null) {
q.setDependentFlag(true);
q.setDependentQuestionId(dep.getQuestionId());
q.setDependentQuestionAnswer(dep.getAnswerValue());
} else {
q.setDependentFlag(false);
}
}
}
/**
* returns a surveyDto populated from the published xml. This domain graph lacks many keyIds so
* it is not suitable for updating the survey structure. It is, however, suitable for rendering
* the survey and collecting responses.
*
* @param surveyId
* @return
*/
@Override
public SurveyDto getPublishedSurvey(String surveyId) {
SurveyDto dto = null;
try {
try {
if (cache != null) {
if (cache.containsKey(PUB_CACHE_PREFIX + surveyId)) {
dto = (SurveyDto) cache
.get(PUB_CACHE_PREFIX + surveyId);
if (dto != null) {
return dto;
}
}
}
} catch (Exception e) {
log.log(Level.WARNING, "Could not check cache", e);
}
URL url = new URL(PropertyUtil.getProperty(SURVEY_S3_PROP)
+ PropertyUtil.getProperty(SURVEY_DIR_PROP) + "/"
+ surveyId + ".xml");
BufferedReader reader = new BufferedReader(new InputStreamReader(
url.openStream(), "UTF-8"));
StringBuffer buff = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
buff.append(line);
}
reader.close();
String fullContent = buff.toString();
if (fullContent.trim().length() > 0) {
SurveyXmlDtoHelper helper = new SurveyXmlDtoHelper();
dto = helper.parseAsDtoGraph(fullContent.trim(), new Long(
surveyId));
try {
if (cache != null) {
cache.put(PUB_CACHE_PREFIX + surveyId, dto);
}
} catch (Exception e) {
log.log(Level.WARNING, "Could not cache result", e);
}
}
} catch (Exception e) {
log.log(Level.SEVERE, "Could not popuate survey from xml", e);
}
return dto;
}
/**
* fires an async request to generate a bootstrap xml file
*
* @param surveyIdList
* @param dbInstructions
* @param notificationEmail
*/
@Override
public void generateBootstrapFile(List<Long> surveyIdList,
String dbInstructions, String notificationEmail) {
StringBuilder buf = new StringBuilder();
if (surveyIdList != null) {
for (int i = 0; i < surveyIdList.size(); i++) {
if (i > 0) {
buf.append(BootstrapGeneratorRequest.DELMITER);
}
buf.append(surveyIdList.get(i).toString());
}
}
Queue queue = QueueFactory.getQueue("background-processing");
queue.add(TaskOptions.Builder
.withUrl("/app_worker/bootstrapgen")
.param(BootstrapGeneratorRequest.ACTION_PARAM,
BootstrapGeneratorRequest.GEN_ACTION)
.param(BootstrapGeneratorRequest.SURVEY_ID_LIST_PARAM,
buf.toString())
.param(BootstrapGeneratorRequest.EMAIL_PARAM, notificationEmail)
.param(BootstrapGeneratorRequest.DB_PARAM,
dbInstructions != null ? dbInstructions : ""));
}
/**
* returns a survey (core info only). If you need all data, use loadFullSurvey.
*/
@Override
public SurveyDto findSurvey(Long id) {
SurveyDto dto = null;
if (id != null) {
Survey s = surveyDao.getById(id);
if (s != null) {
dto = new SurveyDto();
DtoMarshaller.copyToDto(s, dto);
dto.setQuestionGroupList(null);
}
}
return dto;
}
/**
* saves a Message indicating that the survey has been updated and needs to be republished. If
* there is already a message of this type for the surveyId passed in, the last update time
* stamp of the message is updated instead of creating a duplicate message.
*
* @param surveyId
*/
private void saveSurveyUpdateMessage(Long surveyId) {
Message m = null;
List<Message> messages = messageDao.listBySubject(
SURVEY_UPDATE_MESSAGE_ACTION, surveyId, null);
Survey s = surveyDao.getByKey(surveyId);
if (messages != null && messages.size() > 0) {
m = messages.get(0);
m.setLastUpdateDateTime(new Date());
} else {
m = new Message();
m.setActionAbout(SURVEY_UPDATE_MESSAGE_ACTION);
m.setObjectId(surveyId);
m.setShortMessage(SURVEY_UPDATE_MESSAGE);
}
if (s != null) {
m.setObjectTitle(s.getPath() + "/" + s.getName());
}
try {
UserService userService = UserServiceFactory.getUserService();
if (userService != null && userService.isUserLoggedIn()) {
User u = userService.getCurrentUser();
if (u != null) {
m.setUserName(u.getEmail());
}
}
} catch (Exception e) {
log.log(Level.WARNING,
"Could not get current user when publishing message");
}
messageDao.save(m);
}
/**
* marks that a set of changes to a survey are done so we can publish a notification
*
* @param id
*/
@Override
public void markSurveyChangesComplete(Long surveyId) {
Message m = null;
Survey s = surveyDao.getByKey(surveyId);
m = new Message();
m.setActionAbout(SURVEY_CHANGE_COMPLTE_MESSAGE_ACTION);
m.setObjectId(surveyId);
m.setShortMessage(SURVEY_CHANGE_COMPLETE_MESSAGE);
if (s != null) {
m.setObjectTitle(s.getPath() + "/" + s.getName());
}
try {
UserService userService = UserServiceFactory.getUserService();
if (userService != null && userService.isUserLoggedIn()) {
User u = userService.getCurrentUser();
if (u != null) {
m.setUserName(u.getEmail());
}
}
} catch (Exception e) {
log.log(Level.WARNING,
"Could not get current user when publishing message");
}
messageDao.save(m);
}
/**
* lists the base question info for all questions that depend on the questionId passed in
*
* @param questionId
* @return
*/
@Override
public ArrayList<QuestionDto> listQuestionsDependentOnQuestion(
Long questionId) {
QuestionDao dao = new QuestionDao();
List<Question> qList = dao.listQuestionsByDependency(questionId);
ArrayList<QuestionDto> dtoList = null;
if (qList != null) {
dtoList = new ArrayList<QuestionDto>();
for (Question q : qList) {
QuestionDto dto = new QuestionDto();
DtoMarshaller.copyToDto(q, dto);
dtoList.add(dto);
}
}
return dtoList;
}
}