package com.transformuk.bdt.service;
import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.ServletContext;
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.transformuk.bdt.domain.Bfsf;
import com.transformuk.bdt.domain.CrawledContent;
import com.transformuk.bdt.domain.Lep;
import com.transformuk.bdt.domain.Options;
import com.transformuk.bdt.domain.Page;
import com.transformuk.bdt.domain.Questions;
import com.transformuk.bdt.mapit.domain.MapItPartialPostCode;
import com.transformuk.bdt.model.OtherSources;
import com.transformuk.bdt.model.ProfileQuestions;
import com.transformuk.bdt.model.ReportModel;
import com.transformuk.bdt.util.GenericHelper;
import com.transformuk.bdt.util.OtherSourcesComparator;
import com.transformuk.bdt.util.StaticReferences;
@Service
@Configuration
@PropertySource(value = "classpath:conf.properties")
public class ReportServiceImpl implements IReportService {
private static final String TITLE_SUFFIX = "-title";
@Autowired
MapItServiceImpl mapitService;
@Autowired
LepServiceImpl lepService;
@Autowired
BfsfServiceImpl bfsfService;
@Autowired
ExternalBfsfServiceImpl externalBfsfService;
@Autowired
QuestionServiceImpl questionService;
@Autowired
CrawlerService crawlerService;
@Value("${suffix.keywords}")
private String suffixKeywords;
@Value("${es.crawlerqueries.type}")
private String esCrawlerQueryType;
@Value("${es.indexname}")
private String esIndex;
@Value("${es.bfsf.type}")
private String esBfsfType;
@Value("${es.bfsf.external.type}")
private String esExternalType;
@Value("${es.crawler.result.size}")
private String crawlerResultSize;
@Value("${es.crawler.indexname}")
private String esCrawlerIndex;
@Value("${es.crawler.type}")
private String esCrawlerType;
private static final Logger LOGGER = Logger.getLogger(ReportServiceImpl.class);
@Override
public ReportModel buildDataForReport(ProfileQuestions profileQuestions,
HttpSession session) throws Exception {
try {
String postCode = profileQuestions.getValues().get(
StaticReferences.QC_POST_CODE);
ReportModel reportModel = populateReportModel(session, profileQuestions);
reportModel.setPostCode(postCode);
LOGGER.debug("Fetching mapit response for postcode:" + postCode);
boolean isPartialPostCode = (Boolean)session.getAttribute(StaticReferences.IS_PARTIAL_POSTCODE);
ResponseEntity<String> mapItResponse = getMapItDataByCode(postCode, isPartialPostCode);
String jsonResponse = mapItResponse.getBody();
if (isPartialPostCode) {
//if partial post code use another API
jsonResponse = getJsonResponseByPointAPI(jsonResponse);
}
LOGGER.debug("Mapit json response for postcode= " + jsonResponse);
Map<String, List<String>> gssNameResponseMap = mapitService
.getGssNamesFromPostCodeResponse(jsonResponse, isPartialPostCode);
// build lep names/phone data from gss codes
List<String> gssList = gssNameResponseMap.get(StaticReferences.GSS);
reportModel.setLepResult(buildLepData(gssList));
// set if HG based on country name
List<String> countryNameList = gssNameResponseMap.get(StaticReferences.COUNTRY_NAME);
if (!countryNameList.isEmpty()) {
if (StringUtils.equalsIgnoreCase(StaticReferences.ENGLAND, countryNameList.get(0))) {
reportModel.setBasedInEngland(true);
} else {
setAsNotHighGrowth(session);
}
}
// use council code in query, should only contain ONE value
List<String> councilCodeList = gssNameResponseMap.get(StaticReferences.COUNCIL_CODE);
if (!councilCodeList.isEmpty()) {
// remove known suffixes
councilCodeList.set(0, GenericHelper.suffixRemover(councilCodeList.get(0), suffixKeywords));
gssNameResponseMap.put(StaticReferences.COUNCIL_CODE, councilCodeList);
}
String council = gssNameResponseMap.get(StaticReferences.COUNCIL_CODE).get(0);
String region = getRegionfromEUR(gssNameResponseMap);
//query bfsf data
Map<String, List<Bfsf>> bfsfSearchResults = doBfsfSearch(profileQuestions, session, council, region, bfsfService);
reportModel.setSearchResult(bfsfSearchResults);
// build query for crawled data
Map<String, Set<String>> queryTermsForCrawledData = buildQueryTermsByCategoriesForCrawledData(profileQuestions, session);
//query crawler
Map<String, HashSet<CrawledContent>> crawledResults = doCrawledSearch(queryTermsForCrawledData);
//store results from all external i.e. other sources
Map<String, List<OtherSources>> otherSourcesMap = new LinkedHashMap<String, List<OtherSources>>();
addCrawlerResultToMap(crawledResults, otherSourcesMap);
//query External (greatbusiness) data
bfsfSearchResults = doBfsfSearch(profileQuestions, session, council, region, externalBfsfService);
addExtBfsfResultToMap(bfsfSearchResults, otherSourcesMap);
//sort them on their score
sortCrawlerAndExtBfsfResults(otherSourcesMap);
//set the data for report
reportModel.setOtherSources(otherSourcesMap);
return reportModel;
} catch (Exception e) {
LOGGER.error("Exception building report: " + e.getMessage());
throw e;
}
}
private void sortCrawlerAndExtBfsfResults(Map<String, List<OtherSources>> otherSourcesMap) {
for (Entry<String, List<OtherSources>> entry : otherSourcesMap.entrySet()) {
//sort the data
Collections.sort(entry.getValue(),new OtherSourcesComparator());
}
}
private void addExtBfsfResultToMap(Map<String, List<Bfsf>> bfsfSearchResults,
Map<String, List<OtherSources>> otherSourcesMap) {
for (Entry<String, List<Bfsf>> entry : bfsfSearchResults.entrySet()) {
List<OtherSources> otherSourcesList = new ArrayList<OtherSources>();
for (Bfsf bfsf : entry.getValue()) {
OtherSources otherSources = new OtherSources();
otherSources.setDescription(bfsf.getShort_description());
otherSources.setScore(bfsf.get_score());
otherSources.setTitle(bfsf.getTitle());
otherSources.setUrl(bfsf.getWeb_url());
//only add if this source not already added before
//across ALL categories of 'Other Sources'
boolean isDuplicate = isSourceRecordDuplicate(otherSources, otherSourcesMap);
if (!isDuplicate)
otherSourcesList.add(otherSources);
}
//add to existing list if already created by crawler
if (otherSourcesMap.get(entry.getKey()) != null)
otherSourcesMap.get(entry.getKey()).addAll(otherSourcesList);
else
otherSourcesMap.put(entry.getKey(),otherSourcesList);
}
}
private boolean isSourceRecordDuplicate(OtherSources newOtherSources, Map<String, List<OtherSources>> otherSourcesMap) {
//compares the url of new and existing sources to check if duplicate
for (Entry<String, List<OtherSources>> entry : otherSourcesMap.entrySet()) {
for (OtherSources existingOtherSources : entry.getValue()) {
String existingSourceUrl = existingOtherSources.getUrl();
String newSourceUrl = newOtherSources.getUrl();
if ( (StringUtils.isNotBlank(existingSourceUrl)
&& (StringUtils.isNotBlank(newSourceUrl)))
&& (existingSourceUrl.equalsIgnoreCase(newSourceUrl))) {
return true;
}
}
}
return false;
}
private void addCrawlerResultToMap(Map<String, HashSet<CrawledContent>> crawledResults,
Map<String, List<OtherSources>> otherSourcesMap) {
for (Entry<String, HashSet<CrawledContent>> entry : crawledResults.entrySet()) {
List<OtherSources> otherSourcesList = new ArrayList<OtherSources>();
for (CrawledContent crawledContent : entry.getValue()) {
OtherSources otherSources = new OtherSources();
otherSources.setContent(crawledContent.getContent());
otherSources.setDescription(crawledContent.getMetatagDescription());
otherSources.setScore(crawledContent.get_score());
otherSources.setTitle(crawledContent.getTitle());
otherSources.setUrl(crawledContent.getUrl());
otherSourcesList.add(otherSources);
}
otherSourcesMap.put(entry.getKey(), otherSourcesList);
}
}
private String getJsonResponseByPointAPI(String jsonResponse)
throws JsonParseException, JsonMappingException, IOException {
MapItPartialPostCode mapItPostCodeResponse = mapitService.convertDataFromPartialPostCode(jsonResponse);
//make call to the point API
ResponseEntity<String> jsonDataPointApi = mapitService.getDataFromPointApi(mapItPostCodeResponse);
return jsonDataPointApi.getBody();
}
private ResponseEntity<String> getMapItDataByCode(String postCode, boolean isPartialPostCode) {
ResponseEntity<String> mapItResponse = null;
if (isPartialPostCode) {
mapItResponse = mapitService.getMapItDataForPartialPostCode(postCode);
}
else {
mapItResponse = mapitService.getMapItDataForFullPostCode(postCode);
}
if (mapItResponse.getStatusCode() != HttpStatus.OK) {
LOGGER.error("MapIt postcode lookup returned HttpResponse error code: "
+ mapItResponse.getStatusCode());
throw new RuntimeException("MapIt Service error,http code: "+ mapItResponse.getStatusCode());
}
return mapItResponse;
}
@SuppressWarnings("unchecked")
private Map<String, List<Bfsf>> doBfsfSearch(ProfileQuestions profileQuestions,
HttpSession session, String councilCode, String region,
ICommonBfsfService commonBfsfService)
throws Exception {
Map<String, String> categoryMap = (Map<String, String>) session
.getAttribute(StaticReferences.QUERY_CATEGORIES);
// hold results
Map<String, List<Bfsf>> searchResultMap = new LinkedHashMap<String, List<Bfsf>>();
boolean isFinanceQuery = false;
String financeType = (String) session
.getAttribute(StaticReferences.QUERY_FINANCE);
if (StringUtils.isNotEmpty(financeType))
isFinanceQuery = true;
// iterate over all categories chosen by user
for (Map.Entry<String, String> entry : categoryMap.entrySet()) {
boolean isCurrCatFinance = StringUtils.startsWithIgnoreCase(
entry.getKey(), StaticReferences.CATEGORY_FINANCE);
String query = getBfsfQueryByUserInput(session, isFinanceQuery,
isCurrCatFinance);
query = query
.replace(StaticReferences.CATEGORIES, entry.getValue())
.replace(StaticReferences.COUNCIL, councilCode)
.replace(
StaticReferences.STAGE,
(String) session
.getAttribute(StaticReferences.QUERY_STAGE))
.replace(
StaticReferences.SIZE,
(String) session
.getAttribute(StaticReferences.QUERY_SIZE))
.replace(
StaticReferences.SECTOR,
(String) session
.getAttribute(StaticReferences.QUERY_SECTOR))
.replace(StaticReferences.REGION, region);
if (isFinanceQuery && isCurrCatFinance) {
// substitute finance
query = query.replace(StaticReferences.FINANCE_TYPE,
financeType);
}
LOGGER.debug("Firing query for BFSF :" + query);
// fire query and store results
List<Bfsf> resultList = commonBfsfService.searchExtOrBfsfBySource( query, searchResultMap.values());
if (!CollectionUtils.isEmpty(resultList)) {
searchResultMap.put(entry.getKey(), resultList);
}
}
return searchResultMap;
}
private Map<String, HashSet<CrawledContent>> doCrawledSearch(Map<String, Set<String>> queryTermsForCrawledData) throws Exception {
// hold results
Map<String, HashSet<CrawledContent>> searchResultMap = new LinkedHashMap<String, HashSet<CrawledContent>>();
QueryBuilder qb = QueryBuilders.matchAllQuery();
String crawlerQuery = crawlerService.getCrawlerQueryByQueryBuilder(
qb, 0, esIndex, esCrawlerQueryType);
for (Map.Entry<String, Set<String>> entry : queryTermsForCrawledData.entrySet()) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("[");
//static data i.e. {"match": {"url": {"query": "gov.uk","type": "phrase","boost": 0.5}}},
//queryBuilder.append("{\"match\": { \"url\": {\"query\": \"gov.uk\",\"type\": \"phrase\",\"boost\": 0.5}}},");
Set<String> tagSet = entry.getValue();
int tagSize = 0;
for (String tag : tagSet) {
tagSize++;
queryBuilder.append("{\"match\": { \"matchedtags\": {\"query\":");
queryBuilder.append("\"").append(tag).append(TITLE_SUFFIX).append("\",");
queryBuilder.append("\"type\": \"phrase\"}}}");
if (tagSize != tagSet.size())
queryBuilder.append(",");
}
queryBuilder.append("]");
//substitute the dynamic bits of query
String query = crawlerQuery.replace("\"%DYNAMIC_TAGS%\"", queryBuilder.toString());
//make actual query
LOGGER.debug("Firing query for Crawled Data:"+query);
HashSet<CrawledContent> resultList = crawlerService.searchCrawledContentBySource(
query, crawlerResultSize, esCrawlerIndex, esCrawlerType, searchResultMap.values());
if (!CollectionUtils.isEmpty(resultList)) {
searchResultMap.put(entry.getKey(), resultList);
}
}
return searchResultMap;
}
private String getRegionfromEUR(Map<String, List<String>> gssNameResponseMap) {
String eurCode = gssNameResponseMap.get(StaticReferences.NAMES).get(0);
if (StringUtils.equalsIgnoreCase(eurCode, StaticReferences.EASTERN))
return StaticReferences.EAST_OF_ENGLAND;
return eurCode;
}
private List<Lep> buildLepData(List<String> gssList) throws Exception {
List<Lep> lepDataList = new ArrayList<Lep>();
for (String gssCode : gssList) {
List<Lep> lepListForGSSCode = lepService
.getMatchingLepFromGssCode(gssCode);
// note for same gsscode we can get multiple LEPS therefore add all list
lepDataList.addAll(lepListForGSSCode);
}
return lepDataList;
}
private ReportModel populateReportModel(HttpSession session,
ProfileQuestions profileQuestions) {
// reportModel partially already build by controller
ReportModel reportModel = (ReportModel) session
.getAttribute(StaticReferences.REPORT_MODEL);
reportModel.setHighGrowth((Boolean) session
.getAttribute(StaticReferences.HIGH_GROWTH));
String supportAreas = profileQuestions.getValues().get(
StaticReferences.QC_SUPP_ACT);
if (StringUtils.containsIgnoreCase(supportAreas,
StaticReferences.SUPPAREA_EXPORT)) {
reportModel.setExportArea(true);
}
String sector = profileQuestions.getValues().get(
StaticReferences.QC_IND_DD);
if (StringUtils.equalsIgnoreCase(sector,
StaticReferences.SECTOR_MANUFACTURING)) {
reportModel.setManufacturingSector(true);
}
return reportModel;
}
private String getBfsfQueryByUserInput(HttpSession session,
boolean isFinanceQuery, boolean isCurrCatFinance) throws Exception {
// get the bfsf query based on users category selection
ServletContext servletContext = session.getServletContext();
if (isFinanceQuery && isCurrCatFinance) {
// if the query is already stored in the context
String questionQueryFinance = (String) servletContext
.getAttribute(StaticReferences.QUESTION_QUERY_WITH_FINANCE);
if (questionQueryFinance == null) {
QueryBuilder qb = QueryBuilders.boolQuery().must(
matchPhraseQuery("support_types",
StaticReferences.FINANCE_TYPE));
// fire query
questionQueryFinance = bfsfService
.getBfsfQueryByQueryBuilder(qb);
// set it in context for reuse
servletContext.setAttribute(
StaticReferences.QUESTION_QUERY_WITH_FINANCE,
questionQueryFinance);
}
return questionQueryFinance;
} else {
String questionQueryNonFinance = (String) servletContext
.getAttribute(StaticReferences.QUESTION_QUERY_NON_FINANCE);
if (questionQueryNonFinance == null) {
QueryBuilder qb = QueryBuilders.boolQuery().mustNot(
matchPhraseQuery("support_types",
StaticReferences.FINANCE_TYPE));
// fire query
questionQueryNonFinance = bfsfService.getBfsfQueryByQueryBuilder(qb);
// set it in context
servletContext.setAttribute(
StaticReferences.QUESTION_QUERY_NON_FINANCE,
questionQueryNonFinance);
}
return questionQueryNonFinance;
}
}
@SuppressWarnings("unchecked")
private Map<String, Set<String>> buildQueryTermsByCategoriesForCrawledData(
ProfileQuestions profileQuestions, HttpSession session) throws Exception {
//ensure unique elements i.e. set
Map<String, Set<String>> categoryTagsMap = new LinkedHashMap<String, Set<String>>();
List<String> categoryList = (List<String>) session.getAttribute(StaticReferences.CATEGORY_PAGES);
for (String catPageName : categoryList) {
Set<String> termsCrawledList = new LinkedHashSet<String>();
Page page = getPageDetailsByName(catPageName);
if (page.isFireQuery()) {
for (Questions question : page.getQuestions()) {
// add all tags from the questionTag
termsCrawledList.addAll(question.getQuestionTags());
// add all tags from the answer responseTag
termsCrawledList.addAll(buildTermsFromUsersSelectedOption(
question, profileQuestions));
}
categoryTagsMap.put(page.getPageTitle(), termsCrawledList);
}
}
return categoryTagsMap;
}
protected Set<String> buildTermsFromUsersSelectedOption(Questions q,
ProfileQuestions profileQuestions) {
String userAnswerCode = profileQuestions.getValues().get(
q.getQuestionCode());
String[] userAnswersList = StringUtils.split(userAnswerCode, ',');
//only add unique values
Set<String> responseTagList = new HashSet<String>();
for (String answerCode : userAnswersList) {
Options userOption = getOptionByAnswerCode(q, answerCode);
responseTagList.addAll(userOption.getResponseTags());
}
return responseTagList;
}
// returns Option pojo
protected Options getOptionByAnswerCode(Questions q, String userAnswerCode) {
if (q.getOptions() != null) {
for (Options option : q.getOptions()) {
if (StringUtils.equals(option.getAnswerCode(), userAnswerCode)) {
return option;
}
}
}
return null;
}
private Page getPageDetailsByName(String pageName) throws Exception {
QueryBuilder qb = QueryBuilders.matchPhraseQuery(
StaticReferences.QUESTION_QUERY, pageName);
return questionService.searchQuestionsByQueryBuilder(qb).get(0);
}
private void setAsNotHighGrowth(HttpSession session) {
Boolean isHighGrowthBusiness = (Boolean) session
.getAttribute(StaticReferences.HIGH_GROWTH);
if (isHighGrowthBusiness) {
// only set it to false as by default it is HG
session.setAttribute(StaticReferences.HIGH_GROWTH, false);
}
}
}