package net.sf.gazpachoquest.questionnaire.resolver;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.el.ELException;
import javax.el.ExpressionFactory;
import net.sf.gazpachoquest.domain.core.Breadcrumb;
import net.sf.gazpachoquest.domain.core.Question;
import net.sf.gazpachoquest.domain.core.QuestionBreadcrumb;
import net.sf.gazpachoquest.domain.core.Questionnaire;
import net.sf.gazpachoquest.domain.core.QuestionnaireDefinition;
import net.sf.gazpachoquest.domain.core.Section;
import net.sf.gazpachoquest.domain.core.SectionBreadcrumb;
import net.sf.gazpachoquest.qbe.SearchParameters;
import net.sf.gazpachoquest.questionnaire.support.PageMetadataCreator;
import net.sf.gazpachoquest.questionnaire.support.PageStructure;
import net.sf.gazpachoquest.services.BreadcrumbService;
import net.sf.gazpachoquest.services.QuestionService;
import net.sf.gazpachoquest.services.QuestionnaireAnswersService;
import net.sf.gazpachoquest.services.QuestionnaireDefinitionService;
import net.sf.gazpachoquest.services.QuestionnaireService;
import net.sf.gazpachoquest.services.SectionService;
import net.sf.gazpachoquest.types.NavigationAction;
import net.sf.gazpachoquest.types.RandomizationStrategy;
import net.sf.gazpachoquest.types.RenderingMode;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.Assert;
import de.odysseus.el.util.SimpleContext;
public abstract class AbstractResolver<T extends Breadcrumb> implements PageResolver {
private static final Logger logger = LoggerFactory.getLogger(AbstractResolver.class);
protected static final Integer QUESTION_NUMBER_START_COUNTER = 1;
protected static final boolean BREADCRUMB_CACHE_ENABLED = true;
@Autowired
private QuestionnaireAnswersService questionnaireAnswersService;
@Autowired
private BreadcrumbService breadcrumbService;
@Autowired
private SectionService sectionService;
@Autowired
private QuestionService questionService;
@Autowired
private QuestionnaireService questionnaireService;
@Autowired
@Qualifier("questionnaireDefinitionServiceImpl")
protected QuestionnaireDefinitionService questionnaireDefinitionService;
@Autowired
protected PageMetadataCreator metadataCreator;
protected final RenderingMode type;
// @Autowired
// @Qualifier("elFactory")
// protected ExpressionFactory elFactory;
protected ExpressionFactory elFactory = ExpressionFactory.newInstance();
protected AbstractResolver(RenderingMode type) {
this.type = type;
}
@SuppressWarnings("unchecked")
@Override
public PageStructure resolveNextPage(final Questionnaire questionnaire, final NavigationAction action) {
Questionnaire fetchedQuestionnair = questionnaireService.findOne(questionnaire.getId());
QuestionnaireDefinition questionnaireDefinition = fetchedQuestionnair.getQuestionnaireDefinition();
int questionnaireId = questionnaire.getId();
logger.debug("Fetching {} page for questionnaire {}", action.toString(), questionnaireId);
List<Object[]> result = breadcrumbService.findLastAndPosition(questionnaireId);
if (!type.equals(RenderingMode.ALL_IN_ONE)){
Assert.state(result.size() <= 1, "Found more than one visible breadcrumb");
}
T breadcrumb = null;
List<T> lastBreadcrumbs = new ArrayList<>();
List<T> breadcrumbs = null;
Integer lastBreadcrumbPosition = null;
// First time entering the questionnaire
if (result.isEmpty()) {
breadcrumbs = makeBreadcrumbs(questionnaireDefinition, questionnaire);
leaveBreakcrumbs(questionnaire, breadcrumbs);
populateLastBreadcrumbs(lastBreadcrumbs, breadcrumbs);
} else {
// At least one breadcrumb
lastBreadcrumbPosition = (Integer) result.get(0)[1];
breadcrumb = (T) result.get(0)[0];
if (!breadcrumb.getRenderingMode().equals(type)) {
// Clean dirties
breadcrumbService.deleteByExample(
Breadcrumb.withProps().questionnaire(Questionnaire.with().id(questionnaireId).build()).build(),
new SearchParameters());
breadcrumbs = makeBreadcrumbs(questionnaireDefinition, questionnaire);
leaveBreakcrumbs(questionnaire, breadcrumbs);
populateLastBreadcrumbs(lastBreadcrumbs, breadcrumbs);
} else {
if (!type.equals(RenderingMode.ALL_IN_ONE)) {
lastBreadcrumbs.add(breadcrumb);
} else {
for (Object[] row : result) {
breadcrumb = (T) row[0];
lastBreadcrumbs.add(breadcrumb);
}
}
}
}
Map<String, Object> answers = questionnaireAnswersService.findByQuestionnaire(questionnaire);
List<T> nextBreadcrumbs = new ArrayList<>();
if (NavigationAction.ENTERING.equals(action)) {
nextBreadcrumbs = lastBreadcrumbs;
} else {
T nextBreadcrumb;
if (NavigationAction.NEXT.equals(action)) {
nextBreadcrumb = findNextBreadcrumb(questionnaireDefinition, questionnaire, answers, lastBreadcrumbs.get(0),
lastBreadcrumbPosition);
Assert.notNull(nextBreadcrumb, "Page out of range");
lastBreadcrumbs.get(0).setLast(Boolean.FALSE);
breadcrumbService.save(lastBreadcrumbs.get(0));
nextBreadcrumb.setLast(Boolean.TRUE);
if (nextBreadcrumb.isNew()){
questionnaire.addBreadcrumb(nextBreadcrumb);
questionnaireService.save(questionnaire);
}else{
breadcrumbService.save(nextBreadcrumb);
}
} else {// PREVIOUS
nextBreadcrumb = findPreviousBreadcrumb(questionnaireDefinition, questionnaire, lastBreadcrumbs.get(0),
lastBreadcrumbPosition);
Assert.notNull(nextBreadcrumb, "Page out of range");
if (breadcrumbCacheEnable()) {
lastBreadcrumbs.get(0).setLast(Boolean.FALSE);
breadcrumbService.save(lastBreadcrumbs.get(0));
} else {
// Removed displayed questions from breadcrumbs
/*-
Breadcrumb entity = Breadcrumb.withProps()
.questionnaire(Questionnaire.with().id(questionnaire.getId()).build()).build();
breadcrumbService.deleteByExample(entity,
new SearchParameters().after(Breadcrumb_.createdDate, nextBreadcrumb.getCreatedDate()));
*/
questionnaireService.removeBreadcrumb(questionnaire.getId(), lastBreadcrumbs.get(0));
}
nextBreadcrumb.setLast(Boolean.TRUE);
breadcrumbService.save(nextBreadcrumb);
}
// In all renders except AllInOne only is displayed a breadcrumb at a time
nextBreadcrumbs.add(nextBreadcrumb);
}
return createPageStructure(questionnaireDefinition.getRandomizationStrategy(), nextBreadcrumbs, answers);
}
protected abstract T findPreviousBreadcrumb(QuestionnaireDefinition questionnaireDefinition,
Questionnaire questionnaire, T lastBreadcrumb, Integer lastBreadcrumbPosition);
protected abstract T findNextBreadcrumb(QuestionnaireDefinition questionnaireDefinition,
Questionnaire questionnaire, Map<String, Object> answers, T lastBreadcrumb, Integer lastBreadcrumbPosition);
protected abstract List<T> makeBreadcrumbs(QuestionnaireDefinition questionnaireDefinition,
Questionnaire questionnaire);
protected PageStructure createPageStructure(RandomizationStrategy randomizationStrategy, List<T> breadcrumbs, Map<String, Object> answers) {
PageStructure nextPage = new PageStructure();
if (!breadcrumbs.isEmpty()) {
nextPage.setMetadata(metadataCreator.create(randomizationStrategy, type, breadcrumbs.get(0)));
nextPage.setAnswers(answers);
}
return nextPage;
}
protected void leaveBreakcrumbs(final Questionnaire questionnaire, List<T> breadcrumbs) {
for (T newBreadcrumb : breadcrumbs) {
questionnaire.addBreadcrumb(newBreadcrumb);
}
questionnaireService.save(questionnaire);
}
protected List<Question> findQuestions(Section section) {
List<Question> questions;
if (section.isRandomizationEnabled()) {
questions = questionService
.findByExample(Question.with().section(Section.with().id(section.getId()).build()).build(),
new SearchParameters());
shuffle(questions);
} else {
questions = questionService.findBySectionId(section.getId());
}
return questions;
}
protected void populateQuestionsBreadcrumbs(List<T> breadcrumbs, Integer nextQuestionNumber) {
Integer questionNumberCounter = nextQuestionNumber;
for (Breadcrumb breadcrumb : breadcrumbs) {
SectionBreadcrumb sectionBreadcrumb = (SectionBreadcrumb) breadcrumb;
Section section = sectionBreadcrumb.getSection();
List<Question> questions = findQuestions(section);
for (Question question : questions) {
sectionBreadcrumb.addBreadcrumb(QuestionBreadcrumb.with().question(question).last(Boolean.FALSE)
.questionNumber(questionNumberCounter++).build());
}
}
}
private void populateLastBreadcrumbs(List<T> lastBreadcrumbs, List<T> breadcrumbs) {
for (T breadcrumb : breadcrumbs) {
if (!breadcrumb.isLast()) {
break;
}
lastBreadcrumbs.add(breadcrumb);
}
}
protected boolean breadcrumbCacheEnable() {
return BREADCRUMB_CACHE_ENABLED;
}
protected void shuffle(List<Question> questions) {
SecureRandom random = new SecureRandom();
for (int i = 0; i < questions.size(); i++) {
int position = i + random.nextInt(questions.size() - i);
Question temp = questions.get(i);
Question other = questions.get(position);
questions.set(i, other);
questions.set(position, temp);
}
}
protected boolean isRevealed(String relevance, Map<String, Object> answers) {
if (StringUtils.isBlank(relevance)) {
return true;
}
SimpleContext context = new SimpleContext();
for (Entry<String, Object> answer : answers.entrySet()) {
String code = answer.getKey();
Object value = answer.getValue();
if (value != null) {
context.setVariable(code, elFactory.createValueExpression(value, value.getClass()));
}
}
Boolean revealed = false;
try {
// Evaluate the condition
revealed = (Boolean) elFactory.createValueExpression(context, relevance, Boolean.class).getValue(context);
} catch (ELException e) {
logger.warn("Errors found in evaluating the relevance condition", e);
}
return revealed;
}
}