/** * @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); } }