/**
* @author ishaikh
*
*/
package com.transformuk.bdt.controller;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
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.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.util.WebUtils;
import com.transformuk.bdt.domain.Options;
import com.transformuk.bdt.domain.Page;
import com.transformuk.bdt.domain.Questions;
import com.transformuk.bdt.model.DiagnosticQuestions;
import com.transformuk.bdt.model.ProfileQuestions;
import com.transformuk.bdt.model.ReportModel;
import com.transformuk.bdt.service.QuestionServiceImpl;
import com.transformuk.bdt.service.ReportServiceImpl;
import com.transformuk.bdt.util.StaticReferences;
import com.transformuk.bdt.validator.ProfileValidator;
/**
* Main controller to handle the user journey
*
*/
@Controller
@RequestMapping("/diagnosticForm")
@SessionAttributes({"diagnosticQuestions"})
@Configuration
@PropertySource(value = "classpath:conf.properties")
public class WizardController extends BaseWizardController {
private static final String BREADCRUMB_PAGES = "breadcrumbPages";
private static final char COMMA = ',';
private static final String REDIRECT_HOME = "redirect:diagnosticForm";
@Autowired
ReportServiceImpl reportService;
@Autowired
QuestionServiceImpl questionService;
@Autowired
ProfileValidator profileValidator;
private static final Logger LOGGER = Logger.getLogger(WizardController.class);
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
/**
* Handle request to the default page
* @throws Exception
*/
@RequestMapping(method = RequestMethod.GET)
public String setupForm(Model model, HttpServletRequest request) throws Exception {
clearOldSession(request);
model.addAttribute("profilePage", getPageData());
model.addAttribute("diagnosticQuestions", new DiagnosticQuestions());
//initially set all buss to HG
HttpSession newSession = request.getSession();
newSession.setAttribute(StaticReferences.HIGH_GROWTH, true);
return STEP_ZERO;
}
/**
* Handle bread crumb page navigation only
*/
@RequestMapping(params = {"targetPage"}, method = RequestMethod.GET)
public String handleBreadCrumbNavi(@RequestParam("targetPage") int targetPageIndex, HttpSession session) {
//test if session is still active
if (session.getAttribute("diagnosticQuestions") == null) {
LOGGER.info("Session not active, redirecting to home page");
return REDIRECT_HOME;
}
//reset the pageNumber for the jsp to pull right object
session.setAttribute("pageNumber", targetPageIndex);
return handlePrevNav(targetPageIndex);
}
/**
* Handle post requests to the pages
* @throws Exception
*/
@SuppressWarnings("unchecked")
@RequestMapping(method = RequestMethod.POST)
public String submitForm(HttpServletRequest request, HttpServletResponse response,
@ModelAttribute("diagnosticQuestions") DiagnosticQuestions diagnosticQuestions,
BindingResult result, SessionStatus status, HttpSession session,
@RequestParam("_page") int currPageIndex, Model model) throws Exception {
if (userClickedFinished(request)) {
status.setComplete();
session.invalidate();
return REDIRECT_HOME;
}
int targetPage = WebUtils.getTargetPage(request, "_target", currPageIndex);
// 'Prev' clicked
if (userClickedPrevious(currPageIndex, targetPage)) {
session.setAttribute("pageNumber", targetPage); //reset the pageNumber for the jsp to pull right object
if (targetPage == 1) {
session.setAttribute(StaticReferences.HIGH_GROWTH, true); //reset to HG for profile page
}
return handlePrevNav(targetPage);
}
else {
// 'Next' clicked
ProfileQuestions profileQuestions = diagnosticQuestions.getProfileQuestions();
switch (currPageIndex) {
case 0:
profileValidator.validateBusinessStage(profileQuestions, result);
if (result.hasErrors()) {
return STEP_ZERO;
}
//handle happy path
Page prevPage = (Page) model.asMap().get("profilePage");
for (Questions q : prevPage.getQuestions()) {
//get the next page based on user's selected answer
Options userOption = getOptionByAnswerCode(q, profileQuestions.getBusinessStage());
Page nextPage = getPageDetailsByName(userOption.getShowPage()); //the showQuestion element will only have one value
setValuesForUI(session, currPageIndex, nextPage);
if (q.isContainsHighGrowth() && !userOption.isHighGrowth()) {
//user didn't choose HG answer
setAsNotHighGrowth(session);
}
//add the stage for query
session.setAttribute(StaticReferences.QUERY_STAGE, userOption.getResponseQuery());
return STEP_NEXT; //first page only got 1 question
}
break;
default:
Page currentPage = (Page) session.getAttribute("page"+currPageIndex);
if (currentPage == null)
return STEP_ZERO; //browser 'back' click intercepted
profileValidator.validate(profileQuestions, result, currentPage, session);
if (result.hasErrors()) {
//re-populate the model with prev details
model.addAttribute("page"+currPageIndex, currentPage);
model.addAttribute("pageNumber", currPageIndex);
return STEP_NEXT; //same as prev i.e. the error page
}
else {
if (currPageIndex == 1) {
//delete all old data for query and report from session
cleanUpPrevData(session);
}
for (Questions q : currentPage.getQuestions()) {
if (q.isTriage()) { //handle triage question
//get user's category page list for this questionCode
String catSelection = profileQuestions.getValues().get(q.getQuestionCode());
String[] catSelectionList = StringUtils.split(catSelection, COMMA);
List<String> showPagesList = createShowPageListByAnswerCode(q, catSelectionList);
//store user's selection in session (can't it be fetched from the form object ?)
List<String> catPageList = (List<String>)session.getAttribute(StaticReferences.CATEGORY_PAGES);
if (catPageList == null) {
//first time navigation
session.setAttribute(StaticReferences.CATEGORY_PAGES, Collections.synchronizedList(showPagesList));
}
else {
//if this is questionCode 'SuppAct' i.e. on the 2nd page of profile then
//always wipe and recreate the categoryPages again
if (StringUtils.equalsIgnoreCase(StaticReferences.QC_SUPP_ACT, q.getQuestionCode())) {
session.setAttribute(StaticReferences.CATEGORY_PAGES, Collections.synchronizedList(showPagesList));
}
else {
//if we already have this triage page from prev user navigation
//then we need to override its child cat pages with the new selection
//e.g. if 'finance' is already been visited before and it contain child cat pages
//grants,loans etc then override those values with new ones
if (catPageList.contains(currentPage.getPageName())) {
List<String> childCatList = getChildCategoriesForParent(catPageList, currentPage.getPageName());
catPageList.removeAll(childCatList);
//add the new category pages at specific index
int indexOfCurPage = catPageList.indexOf(currentPage.getPageName());
catPageList.addAll(++indexOfCurPage, showPagesList);
//remove all prev category from category query
removePrevCategoryFromQuery(session, childCatList);
}
else {
//first time user navigation to this triage page
catPageList.addAll(showPagesList);
}
}
}
//only get the immediate next page to show
Page nextPage = getPageDetailsByName(showPagesList.get(0));
//set form object with values to be shown on UI
setValuesForUI(session, currPageIndex, nextPage);
//set attributes required for elastic query
setQueryAttributes(session, currentPage, q, profileQuestions);
//build the category pages for breadcrumbs
setCatBreadCrumbs(session);
return STEP_NEXT;
}
else if ((currPageIndex == 1) && questionIsForReport(q)) { //only for second page of profile
// start adding value to reportModel for report page
String narrative = getNarrativeForAnswer(profileQuestions, q);
ReportModel reportModel = getReportModel(session, narrative);
buildReportModel(q, narrative, reportModel);
session.setAttribute(StaticReferences.REPORT_MODEL, reportModel);
}
if(q.isContainsHighGrowth() && !answeredAsNotHG(q, profileQuestions)) {
//user didn't choose HG answer
setAsNotHighGrowth(session);
}
setQueryAttributes(session, currentPage, q, profileQuestions);
}//end for
//handle non-triage based page to drive next page
if (session.getAttribute(StaticReferences.CATEGORY_PAGES) != null) {
List<String> catPageList = (List<String>)session.getAttribute(StaticReferences.CATEGORY_PAGES);
String lastElement = catPageList.get(catPageList.size() - 1);
if (catPageList.contains(currentPage.getPageName())
&& currentPage.getPageName().equalsIgnoreCase(lastElement)) {
//on last page build and show report page
reportService.buildDataForReport(profileQuestions, session);
return REPORT_PAGE;
}
else {
int indexOfCurPage = catPageList.indexOf(currentPage.getPageName());
String pageName = catPageList.get(indexOfCurPage + 1); //next page
//ASSUMPTION: the showPage element will only have one value
Page nextPage = getPageDetailsByName(pageName);
setValuesForUI(session, currPageIndex, nextPage);
return STEP_NEXT;
}
}
}
break;
}
//if the question is of type 'triage' then don't need to build a query string
return STEP_NEXT;
}
}
@SuppressWarnings("unchecked")
private void setCatBreadCrumbs(HttpSession session) throws Exception {
Map<Integer, String> categoryPagesMap = new LinkedHashMap<Integer,String>();
List<String> pageNavList = (List<String>)session.getAttribute(StaticReferences.CATEGORY_PAGES);
int i = 2; //used as targetPage on UI, starts with 2 as we have 0 and 1 as profile pages
for (String catPageName : pageNavList) {
Page page = getPageDetailsByName(catPageName);
categoryPagesMap.put(i, page.getPageTitle().trim());
i++;
}
session.setAttribute(BREADCRUMB_PAGES, categoryPagesMap);
}
@ModelAttribute("profilePage")
public Page getPageData() throws Exception {
return getPageDetailsByName("profile");
}
@ExceptionHandler(HttpSessionRequiredException.class)
public String SessionException(HttpSessionRequiredException ex) {
LOGGER.info("Session exception: "+ex.getMessage());
return REDIRECT_HOME;
}
private void setValuesForUI(HttpSession session, int currPage, Page nextPage) {
int pageNumber = currPage + 1;
session.setAttribute("page"+pageNumber, nextPage);
session.setAttribute("pageNumber", pageNumber); //for ui
}
private List<String> getChildCategoriesForParent(List<String> catPageList, String currentPageName) throws Exception {
List<String> childPageList = new ArrayList<String>();
for (String catPageName : catPageList) {
Page page = getPageDetailsByName(catPageName);
if (StringUtils.equalsIgnoreCase(page.getParentPageName(), currentPageName)) {
//this page is the child of the current page therefore
childPageList.add(page.getPageName());
}
}
return childPageList;
}
private Page getPageDetailsByName(String pageName) throws Exception {
QueryBuilder qb = QueryBuilders.matchPhraseQuery(StaticReferences.QUESTION_QUERY, pageName);
return questionService.searchQuestionsByQueryBuilder(qb).get(0);
}
}